شروع به کار با gRPC-Rust - Streaming

1. مقدمه

در این کد لبه، شما از gRPC-Rust برای ایجاد یک کلاینت و سرور استفاده خواهید کرد که پایه و اساس برنامه نقشه برداری مسیر نوشته شده در Rust را تشکیل می دهد.

در پایان آموزش، شما یک کلاینت خواهید داشت که با استفاده از gRPC به یک سرور راه دور متصل می شود تا اطلاعاتی در مورد ویژگی های مسیر کلاینت به دست آورد، خلاصه ای از مسیر مشتری ایجاد کند و اطلاعات مسیر مانند به روز رسانی ترافیک را با سرور و سایر کلاینت ها مبادله کند.

این سرویس در یک فایل Protocol Buffers تعریف شده است که از آن برای تولید کد boilerplate برای کلاینت و سرور استفاده می شود تا بتوانند با یکدیگر ارتباط برقرار کنند و در زمان و تلاش شما در اجرای آن عملکرد صرفه جویی شود.

این کد تولید شده نه تنها از پیچیدگی های ارتباط بین سرور و کلاینت، بلکه سریال سازی و سریال سازی داده ها نیز مراقبت می کند.

چیزی که یاد خواهید گرفت

  • نحوه استفاده از بافرهای پروتکل برای تعریف API سرویس.
  • نحوه ساخت یک کلاینت و سرور مبتنی بر gRPC از تعریف بافرهای پروتکل با استفاده از تولید کد خودکار.
  • درک ارتباط جریان مشتری-سرور با gRPC.

این کد لبه برای توسعه دهندگان Rust که تازه به gRPC می پردازند یا به دنبال یک تجدید کننده gRPC هستند یا هر فرد دیگری که علاقه مند به ساختن سیستم های توزیع شده است، طراحی شده است. هیچ تجربه قبلی gRPC مورد نیاز نیست.

2. قبل از شروع

پیش نیازها

مطمئن شوید که موارد زیر را نصب کرده اید:

  • شورای همکاری خلیج فارس دستورالعمل های اینجا را دنبال کنید
  • Rust ، آخرین نسخه. دستورالعمل های نصب را در اینجا دنبال کنید.

کد را دریافت کنید

برای اینکه مجبور نباشید به طور کامل از ابتدا شروع کنید، این کد لبه داربستی از کد منبع برنامه را برای شما فراهم می کند تا آن را تکمیل کنید. مراحل زیر به شما نشان می دهد که چگونه برنامه را تکمیل کنید، از جمله استفاده از افزونه های کامپایلر بافر پروتکل برای تولید کد gRPC دیگ بخار.

ابتدا پوشه کاری codelab و cd در آن ایجاد کنید:

mkdir streaming-grpc-rust-getting-started && cd streaming-grpc-rust-getting-started

کد لبه را دانلود و استخراج کنید:

curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
  | tar xvz --strip-components=4 \
  grpc-codelabs-1/codelabs/grpc-rust-streaming/start_here

یا می توانید فایل .zip را که فقط شامل دایرکتوری Codelab است دانلود کرده و به صورت دستی آن را از حالت فشرده خارج کنید.

اگر می خواهید از تایپ کردن در یک پیاده سازی صرفنظر کنید، کد منبع تکمیل شده در GitHub موجود است.

3. پیام ها و خدمات را تعریف کنید

اولین قدم شما این است که سرویس gRPC برنامه، روش های RPC آن، و انواع پیام درخواست و پاسخ آن را با استفاده از بافرهای پروتکل تعریف کنید. خدمات شما ارائه خواهد کرد:

  • متدهای RPC به نام ListFeatures ، RecordRoute و RouteChat که سرور پیاده سازی می کند و کلاینت فراخوانی می کند.
  • انواع پیام Point ، Feature ، Rectangle ، RouteNote و RouteSummary که ساختارهای داده ای هستند که هنگام فراخوانی روش های بالا بین مشتری و سرور رد و بدل می شوند.

این روش‌های RPC و انواع پیام‌های آن همگی در فایل proto/routeguide.proto کد منبع ارائه شده تعریف می‌شوند.

بافرهای پروتکل معمولاً به عنوان پروتوباف شناخته می شوند. برای اطلاعات بیشتر در مورد اصطلاحات gRPC، به مفاهیم اصلی، معماری و چرخه حیات gRPC مراجعه کنید.

تعریف انواع پیام

بیایید ابتدا پیام های خود را تعریف کنیم که توسط RPC های ما استفاده می شود. در فایل routeguide/route_guide.proto کد منبع، ابتدا نوع پیام Point را تعریف کنید. یک Point نشان دهنده یک جفت مختصات طول و عرض جغرافیایی بر روی نقشه است. برای این کد، از اعداد صحیح برای مختصات استفاده کنید:

message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

اعداد 1 و 2 شماره های شناسه منحصر به فرد برای هر یک از فیلدهای ساختار message هستند.

سپس نوع پیام Feature را تعریف کنید. یک Feature از یک فیلد string برای نام یا آدرس پستی چیزی در مکانی مشخص شده توسط یک Point استفاده می کند:

message Feature {
  // The name or address of the feature.
  string name = 1;

  // The point where the feature is located.
  Point location = 2;
}

بعد یک پیام Rectangle که نشان دهنده یک مستطیل طول و عرض جغرافیایی است که به صورت دو نقطه مورب مخالف "lo" و "hi" نمایش داده می شود.

message Rectangle {
  // One corner of the rectangle.
  Point lo = 1;

  // The other corner of the rectangle.
  Point hi = 2;
}

همچنین یک پیام RouteNote که نشان دهنده پیامی است که در یک نقطه خاص ارسال شده است.

message RouteNote {
  // The location from which the message is sent.
  Point location = 1;

  // The message to be sent.
  string message = 2;
}

ما همچنین به یک پیام RouteSummary نیاز داریم. این پیام در پاسخ به RecordRoute RPC دریافت می شود که در قسمت بعدی توضیح داده شده است. این شامل تعداد نقاط دریافتی جداگانه، تعداد ویژگی های شناسایی شده، و کل مسافت طی شده به عنوان مجموع تجمعی فاصله بین هر نقطه است.

message RouteSummary {
  // The number of points received.
  int32 point_count = 1;

  // The number of known features passed while traversing the route.
  int32 feature_count = 2;

  // The distance covered in metres.
  int32 distance = 3;

  // The duration of the traversal in seconds.
  int32 elapsed_time = 4;
}

روش های خدمات را تعریف کنید

بیایید ابتدا سرویس خود را تعریف کنیم و بعد پیام های خود را تعریف کنیم. برای تعریف یک سرویس، یک سرویس با نام را در فایل .proto خود مشخص می کنید. فایل proto/routeguide.proto دارای یک ساختار service به نام RouteGuide است که یک یا چند روش ارائه شده توسط سرویس برنامه را تعریف می کند.

روش های RPC را در تعریف سرویس خود تعریف کنید و نوع درخواست و پاسخ آنها را مشخص کنید. در این بخش از Codelab، اجازه دهید تعریف کنیم:

لیست ویژگی ها

Feature موجود در Rectangle داده شده را به دست می آورد. نتایج به‌جای بازگردانی یک‌باره پخش می‌شوند (مثلاً در یک پیام پاسخ با یک فیلد تکراری)، زیرا مستطیل ممکن است یک منطقه بزرگ را پوشش دهد و دارای تعداد زیادی ویژگی باشد.

یک نوع مناسب برای این RPC یک RPC استریم سمت سرور است: کلاینت درخواستی را به سرور ارسال می کند و جریانی برای خواندن دنباله ای از پیام ها دریافت می کند. مشتری از جریان برگشتی می خواند تا زمانی که پیام دیگری وجود نداشته باشد. همانطور که در مثال ما می بینید، با قرار دادن کلمه کلیدی stream قبل از نوع پاسخ، یک روش پخش سمت سرور را مشخص می کنید.

rpc ListFeatures(Rectangle) returns (stream Feature) {}

RecordRoute

جریانی از Point s را در مسیری که در حال پیمایش است می پذیرد، و پس از تکمیل پیمایش، یک RouteSummary برمی گرداند.

یک RPC جریان سمت کلاینت در این مورد مناسب به نظر می رسد: مشتری دنباله ای از پیام ها را می نویسد و آنها را دوباره با استفاده از جریان ارائه شده به سرور ارسال می کند. هنگامی که مشتری نوشتن پیام ها را به پایان رساند، منتظر می ماند تا سرور همه آنها را بخواند و پاسخ خود را برگرداند. شما با قرار دادن کلمه کلیدی stream قبل از نوع درخواست، یک روش پخش سمت مشتری را مشخص می کنید.

rpc RecordRoute(stream Point) returns (RouteSummary) {}

روت چت

جریانی از RouteNote را می‌پذیرد که هنگام عبور از یک مسیر، در حالی که سایر RouteNote (مثلاً از سایر کاربران) دریافت می‌کند.

این دقیقاً همان مورد استفاده برای پخش جریانی دو طرفه است. یک جریان دوطرفه RPC دارای هر دو طرف دنباله ای از پیام ها با استفاده از جریان خواندن و نوشتن است. این دو جریان به طور مستقل عمل می کنند، بنابراین مشتریان و سرورها می توانند به هر ترتیبی که دوست دارند بخوانند و بنویسند.

به عنوان مثال، سرور می‌تواند منتظر بماند تا تمام پیام‌های مشتری را قبل از نوشتن پاسخ‌های خود دریافت کند، یا می‌تواند متناوب یک پیام را بخواند و سپس یک پیام بنویسد، یا ترکیب دیگری از خواندن و نوشتن.

ترتیب پیام ها در هر جریان حفظ می شود. شما این نوع روش را با قرار دادن کلمه کلیدی stream قبل از درخواست و پاسخ مشخص می کنید.

rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}

4. کد کلاینت و سرور را تولید کنید

ما قبلا کد تولید شده از فایل .proto را در دایرکتوری تولید شده به شما داده ایم.

اگر می‌خواهید نحوه تولید کد از فایل .proto را خودتان یاد بگیرید یا هر گونه تغییری در فایل .proto ایجاد کنید و آنها را آزمایش کنید، به این دستورالعمل‌ها مراجعه کنید.

کد تولید شده شامل:

  • تعاریف ساختاری برای انواع پیام Point , Feature , Rectangle , RouteNote و RouteSummary .
  • یک ویژگی سرویس که باید پیاده سازی کنیم: route_guide_server::RouteGuide .
  • یک نوع کلاینت که برای فراخوانی سرور استفاده می کنیم: route_guide_client::RouteGuideClient<T> .

در مرحله بعد، روش‌ها را در سمت سرور پیاده‌سازی می‌کنیم، به طوری که وقتی مشتری درخواستی را ارسال می‌کند، سرور بتواند با پاسخ پاسخ دهد.

5. سرویس را پیاده سازی کنید

ابتدا بیایید نحوه ایجاد سرور RouteGuide را بررسی کنیم. دو بخش برای اینکه سرویس RouteGuide ما کار خود را انجام دهد وجود دارد:

  • پیاده سازی رابط سرویس ایجاد شده از تعریف سرویس ما: انجام "کار" واقعی سرویس ما.
  • اجرای یک سرور gRPC برای گوش دادن به درخواست های مشتریان و ارسال آنها به روش صحیح پیاده سازی.

در src/server/server.rs ، می‌توانیم کد تولید شده را از طریق include_generated_proto! ماکرو و ویژگی RouteGuide و Point را وارد کنید.

mod grpc_pb {
    grpc::include_generated_proto!("generated", "routeguide");
}

pub use grpc_pb::{
    route_guide_server::{RouteGuideServer, RouteGuide},
    Point, Feature, Rectangle, RouteNote, RouteSummary
};

ما می توانیم با تعریف یک ساختار برای نشان دادن خدمات خود شروع کنیم. فعلاً می توانیم این کار را در src/server/server.rs انجام دهیم:

#[derive(Debug)]
pub struct RouteGuideService {
    features: Vec<Feature>,
}

اکنون، باید ویژگی route_guide_server::RouteGuide از کد تولید شده خود پیاده سازی کنیم.

RouteGuide را پیاده سازی کنید

ما باید رابط RouteGuide تولید شده را پیاده سازی کنیم. اجرا اینگونه به نظر می رسد. این قبلاً در قالب موجود است.

#[tonic::async_trait]
impl RouteGuide for RouteGuideService {
    async fn list_features(
        &self,
        request: Request<Rectangle>,
    ) -> Result<Response<ListFeaturesStream>, Status> {
        ...
    }

    async fn record_route(
        &self,
        request: Request<tonic::Streaming<Point>>,
    ) -> Result<Response<RouteSummary>, Status> {
        ...
    }

    async fn route_chat(
        &self,
        request: Request<tonic::Streaming<RouteNote>>,
    ) -> Result<Response<RouteChatStream>, Status> {
        ...
    }
}

اجازه دهید هر پیاده سازی RPC را با جزئیات بررسی کنیم.

RPC استریم سمت سرور

بیایید با ListFeatures شروع کنیم. این یک RPC استریم سمت سرور است، بنابراین باید چندین Feature را به مشتری خود ارسال کنیم.

async fn list_features(
        &self,
        request: Request<Rectangle>,
    ) -> Result<Response<ListFeaturesStream>, Status> {
        println!("ListFeatures = {:?}", request);

        let (tx, rx) = mpsc::channel(4);
        let features = self.features.clone();

        tokio::spawn(async move {
            for feature in &features[..] {
                if in_range(&feature.location().to_owned(), request.get_ref()) {
                    println!("  => send {feature:?}");
                    tx.send(Ok(feature.clone())).await.unwrap();
                }
            }
            println!(" /// done sending");
        });

        let output_stream = ReceiverStream::new(rx);
        Ok(Response::new(Box::pin(output_stream)))
    }

همانطور که می بینید، یک شی درخواست دریافت می کنیم ( Rectangle که مشتری ما می خواهد Features در آن پیدا کند). این بار، باید جریانی از مقادیر را برگردانیم. ما یک کانال ایجاد می کنیم و یک کار ناهمزمان جدید ایجاد می کنیم که در آن جستجو را انجام می دهیم و ویژگی هایی را که محدودیت های ما را برآورده می کند به کانال ارسال می کنیم. نیمی از کانال به تماس گیرنده بازگردانده می شود و در یک tonic::Response .

جریان RPC سمت مشتری

حالا بیایید به چیز کمی پیچیده‌تر نگاه کنیم: روش پخش سمت سرویس گیرنده RecordRoute ، که در آن جریانی از Points را از مشتری دریافت می‌کنیم و یک RouteSummary با اطلاعاتی در مورد سفر آنها برمی‌گردانیم. یک جریان به عنوان ورودی دریافت می کند که سرور می تواند از آن برای خواندن و نوشتن پیام ها استفاده کند. می تواند از طریق پیام های مشتری با استفاده از متد next() خود تکرار شود و پاسخ تکی خود را برگرداند.

async fn record_route(
        &self,
        request: Request<tonic::Streaming<Point>>,
    ) -> Result<Response<RouteSummary>, Status> {
        println!("RecordRoute");
        let mut stream = request.into_inner();
        let mut summary = RouteSummary::default();
        let mut last_point = None;
        let now = Instant::now();

        while let Some(point) = stream.next().await {
            let point = point?;
            println!("  ==> Point = {point:?}");

            // Increment the point count
            summary.set_point_count(summary.point_count() + 1);

            // Find features
            for feature in &self.features[..] {
                if feature.location().latitude() == point.latitude() {
                    if feature.location().longitude() == point.longitude(){
                        summary.set_feature_count(summary.feature_count() + 1);
                    }
                }
            }

            // Calculate the distance
            if let Some(ref last_point) = last_point {
                let new_dist = summary.distance() + calc_distance(last_point, &point);
                summary.set_distance(new_dist);
            }
            last_point = Some(point);
        }
        summary.set_elapsed_time(now.elapsed().as_secs() as i32);
        Ok(Response::new(summary))
    }

در بدنه متد، از متد next() استریم برای خواندن مکرر درخواست‌های کلاینت به یک شی درخواست (در این مورد یک Point ) استفاده می‌کنیم تا زمانی که دیگر پیامی وجود نداشته باشد. اگر این None باشد، جریان همچنان خوب است و می‌تواند به خواندن ادامه دهد.

جریان دو طرفه RPC

در نهایت، اجازه دهید به جریان دو طرفه RPC RouteChat() نگاه کنیم.

async fn route_chat(
        &self,
        request: Request<tonic::Streaming<RouteNote>>,
    ) -> Result<Response<RouteChatStream>, Status> {
        println!("RouteChat");

        let mut notes: HashMap<(i32, i32), Vec<RouteNote>> = HashMap::new();
        let mut stream = request.into_inner();

        let output = async_stream::try_stream! {
            while let Some(note) = stream.next().await {
                let note = note?;
                let location = note.location();
                let key = (location.latitude(), location.longitude());
                let location_notes = notes.entry(key).or_insert(vec![]);
                location_notes.push(note);
                for note in location_notes {
                    yield note.clone();
                }
            }
        };
        Ok(Response::new(Box::pin(output)))
    }

این بار جریانی دریافت می‌کنیم که مانند مثال استریم سمت مشتری، می‌تواند برای خواندن و نوشتن پیام‌ها استفاده شود. با این حال، این بار ما مقادیر را از طریق جریان روش خود در حالی که مشتری هنوز در حال نوشتن پیام به جریان پیام خود است، برمی گردانیم. نحو برای خواندن و نوشتن در اینجا بسیار شبیه به روش پخش جریانی مشتری ما است، به جز اینکه سرور یک RouteChatStream را برمی گرداند. اگرچه هر یک از طرفین همیشه پیام های طرف مقابل را به ترتیبی که نوشته شده دریافت می کند، هم مشتری و هم سرور می توانند به هر ترتیبی بخوانند و بنویسند - جریان ها کاملاً مستقل عمل می کنند.

ما جریان output را با استفاده از try_stream! ، که نشان می دهد که جریان می تواند خطاها را برگرداند.

سرور را راه اندازی کنید

هنگامی که این روش را پیاده سازی کردیم، همچنین باید یک سرور gRPC راه اندازی کنیم تا مشتریان بتوانند واقعاً از خدمات ما استفاده کنند. main() را پر کنید.

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "[::1]:10000".parse().unwrap();
    println!("RouteGuideServer listening on: {addr}");
    let route_guide = RouteGuideService {
        features: load(),
    };
    let svc = RouteGuideServer::new(route_guide);
    Server::builder().add_service(svc).serve(addr).await?;
    Ok(())
}

در اینجا آنچه در main() اتفاق می افتد، گام به گام است:

  1. پورتی را که می خواهیم برای گوش دادن به درخواست های مشتری استفاده کنیم را مشخص کنید
  2. یک RouteGuideService با ویژگی های بارگذاری شده ایجاد کنید
  3. یک نمونه از سرور gRPC را با استفاده از RouteGuideServer::new() با استفاده از سرویسی که ایجاد کردیم ایجاد کنید.
  4. اجرای سرویس ما را با سرور gRPC ثبت کنید.
  5. serve() روی سرور با جزئیات پورت ما فراخوانی کنید تا منتظر بمانید تا فرآیند کشته شود.

6. مشتری ایجاد کنید

در این بخش، ما به ایجاد یک مشتری Rust برای سرویس RouteGuide در src/client/client.rs نگاه خواهیم کرد.

ابتدا کد تولید شده را وارد محدوده کنید.

mod grpc_pb {
    grpc::include_generated_proto!("generated", "routeguide");
}

use grpc_pb::route_guide_client::RouteGuideClient;
use grpc_pb::{Point, Rectangle, RouteNote};

روش های خدمات تماس

حال بیایید ببینیم که چگونه روش‌های خدمات خود را فراخوانی می‌کنیم. در gRPC-Rust، RPCها در حالت مسدود کردن/همگام عمل می‌کنند، به این معنی که تماس RPC منتظر پاسخ سرور می‌ماند و یا یک پاسخ یا یک خطا را برمی‌گرداند.

RPC استریم سمت سرور

در اینجا ما روش پخش سمت سرور را ListFeatures می نامیم که جریانی از اشیاء Feature جغرافیایی را برمی گرداند.

async fn print_features(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
    let rectangle = proto!(Rectangle {
        lo: proto!(Point {
            latitude: 400_000_000,
            longitude: -750_000_000,
        }),
        hi: proto!(Point {
            latitude: 420_000_000,
            longitude: -730_000_000,
        }),
    });

    let mut stream = client
        .list_features(Request::new(rectangle))
        .await?
        .into_inner();

    while let Some(feature) = stream.message().await? {
        println!("FEATURE: Name = \"{}\", Lat = {}, Lon = {}",
            feature.name(),
            feature.location().latitude(),
            feature.location().longitude());
        }
    Ok(())
}

روش را یک درخواست ارسال می کنیم و نمونه ای از ListFeaturesStream برمی گردانیم. مشتری می تواند از جریان ListFeaturesStream برای خواندن پاسخ های سرور استفاده کند. ما از متد message() ListFeaturesStream برای خواندن مکرر پاسخ‌های سرور به یک شی بافر پروتکل پاسخ (در این مورد Feature ) استفاده می‌کنیم تا زمانی که دیگر پیامی وجود نداشته باشد.

جریان RPC سمت مشتری

در اینجا برای record_route ، بردار نقاط را به یک جریان تبدیل می کنیم. سپس این جریان را به عنوان یک درخواست به record_route() ارسال می کنیم و پس از پردازش کامل جریان توسط سرور، یک پاسخ RouteSummary دریافت می کنیم.

async fn run_record_route(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
    let mut rng = rand::rng();
    let point_count: i32 = rng.random_range(2..100);

    let mut points = vec![];
    for _ in 0..=point_count {
        points.push(random_point(&mut rng))
    }

    println!("Traversing {} points", points.len());
    let request = Request::new(tokio_stream::iter(points));

    match client.record_route(request).await {
        Ok(response) => {
            let response = response.into_inner();
            println!("SUMMARY: Feature Count = {}, Distance = {}", response.feature_count(), response.distance())},
        Err(e) => println!("something went wrong: {e:?}"),
    }

    Ok(())
}

جریان دو طرفه RPC

در نهایت، اجازه دهید به جریان دو طرفه RPC RouteChat() نگاه کنیم. روش درخواست جریانی را ارسال می‌کنیم که به آن می‌نویسیم و جریانی را که می‌توانیم پیام‌ها را از آن بخوانیم، برمی‌گردانیم. این بار در حالی که سرور همچنان در حال نوشتن پیام به جریان پیام خود است، مقادیر را از طریق جریان روش خود برمی گردانیم.

async fn run_route_chat(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
    let start = time::Instant::now();
    let outbound = async_stream::stream! {
        let mut interval = time::interval(Duration::from_secs(1));
        for _ in 0..10 {
            let time = interval.tick().await;
            let elapsed = time.duration_since(start);
            let note = proto!(RouteNote {
                location: proto!(Point {
                    latitude: 409146138 + elapsed.as_secs() as i32,
                    longitude: -746188906,
                }),
                message: format!("at {elapsed:?}"),
            });
            yield note;
        }
    };
    let response = client.route_chat(Request::new(outbound)).await?;
    let mut inbound = response.into_inner();
    while let Some(note) = inbound.message().await? {
        println!("Note: Latitude = {}, Longitude = {}, Message = \"{}\"",
            note.location().latitude(),
            note.location().longitude(),
            note.message());
        }
    Ok(())
}

اگرچه هر یک از طرفین همیشه پیام های طرف مقابل را به ترتیبی که نوشته شده دریافت می کند، هم مشتری و هم سرور می توانند به هر ترتیبی بخوانند و بنویسند - جریان ها کاملاً مستقل عمل می کنند.

فراخوانی روش های کمکی

برای فراخوانی روش های سرویس، ابتدا باید کانالی برای ارتباط با سرور ایجاد کنیم. ما این را با ایجاد یک نقطه پایانی، اتصال به آن نقطه پایانی و عبور کانال ساخته شده هنگام اتصال به RouteGuideClient::new() به صورت زیر ایجاد می کنیم:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create endpoint to connect to
    let endpoint = Endpoint::new("http://[::1]:10000")?; 
    let channel = endpoint.connect().await?;             

    // Create a new client
    let mut client = RouteGuideClient::new(channel); 
    Ok(())
}

در main() متدهایی را که ایجاد کردیم اجرا کنید.

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create endpoint to connect to
    let endpoint = Endpoint::new("http://[::1]:10000")?; 
    let channel = endpoint.connect().await?;             

    // Create a new client
    let mut client = RouteGuideClient::new(channel);

    println!("\n*** SERVER STREAMING ***");
    print_features(&mut client).await?;

    println!("\n*** CLIENT STREAMING ***");
    run_record_route(&mut client).await?;

    println!("\n*** BIDIRECTIONAL STREAMING ***");
    run_route_chat(&mut client).await?;

    Ok(())
}

7. آن را امتحان کنید

ابتدا، برای اجرای Client و Server، بیایید آنها را به عنوان اهداف باینری به جعبه خود اضافه کنیم. ما باید Cargo.toml خود را بر این اساس ویرایش کنیم:

[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"

[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"

مانند هر پروژه دیگری، ما همچنین باید به وابستگی هایی که برای کارکرد کد ما ضروری است فکر کنیم. برای پروژه‌های Rust، وابستگی‌ها در Cargo.toml خواهند بود. ما قبلا وابستگی های لازم را در فایل Cargo.toml لیست کرده ایم.

سپس دستورات زیر را از دایرکتوری های کاری ما اجرا کنید:

  1. سرور را در یک ترمینال اجرا کنید:
RUSTFLAGS="-Awarnings" cargo run --bin routeguide-server 
  1. کلاینت را از ترمینال دیگری اجرا کنید:
RUSTFLAGS="-Awarnings" cargo run --bin routeguide-client

خروجی را مانند این خواهید دید:

*** SERVER STREAMING ***
FEATURE: Name = "Patriots Path, Mendham, NJ 07945, USA", Lat = 407838351, Lon = -746143763
FEATURE: Name = "101 New Jersey 10, Whippany, NJ 07981, USA", Lat = 408122808, Lon = -743999179
FEATURE: Name = "U.S. 6, Shohola, PA 18458, USA", Lat = 413628156, Lon = -749015468
...
*** CLIENT STREAMING ***
Traversing 86 points
SUMMARY: Feature Count = 0, Distance = 803709356

*** BIDIRECTIONAL STREAMING ***
Note: Latitude = 409146138, Longitude = -746188906, Message = "at 112.45µs"
Note: Latitude = 409146139, Longitude = -746188906, Message = "at 1.00011245s"
Note: Latitude = 409146140, Longitude = -746188906, Message = "at 2.00011245s"

8. بعدی چه خواهد شد