1. מבוא
ב-Codelab הזה תשתמשו ב-gRPC-Rust כדי ליצור לקוח ושרת שיהוו את הבסיס לאפליקציה למיפוי מסלולים שנכתבה ב-Rust.
בסוף המדריך יהיה לכם לקוח שמתחבר לשרת מרוחק באמצעות gRPC כדי לקבל מידע על תכונות במסלול של לקוח, ליצור סיכום של המסלול של לקוח ולהחליף מידע על מסלולים, כמו עדכוני תנועה, עם השרת ועם לקוחות אחרים.
השירות מוגדר בקובץ Protocol Buffers, שישמש ליצירת קוד boilerplate ללקוח ולשרת, כדי שהם יוכלו לתקשר זה עם זה. כך תוכלו לחסוך זמן ומאמץ בהטמעת הפונקציונליות הזו.
הקוד שנוצר מטפל לא רק במורכבות של התקשורת בין השרת ללקוח, אלא גם בסריאליזציה ובדה-סריאליזציה של הנתונים.
מה תלמדו
- איך משתמשים ב-Protocol Buffers כדי להגדיר API של שירות.
- איך לבנות לקוח ושרת מבוססי gRPC מהגדרה של Protocol Buffers באמצעות יצירת קוד אוטומטית.
- הבנה של תקשורת סטרימינג בין שרתים ללקוחות באמצעות gRPC.
ה-Codelab הזה מיועד למפתחי Rust שחדשים ב-gRPC או שרוצים לרענן את הידע שלהם ב-gRPC, או לכל מי שמעוניין ליצור מערכות מבוזרות. לא נדרש ניסיון קודם ב-gRPC.
2. לפני שמתחילים
דרישות מוקדמות
ודאו שהתקנתם את הפריטים הבאים:
קבל את הקוד
כדי שלא תצטרכו להתחיל מאפס, ב-Codelab הזה יש תבנית של קוד המקור של האפליקציה שתוכלו להשלים. בשלבים הבאים מוסבר איך לסיים את האפליקציה, כולל שימוש בתוספים של מקמפל מאגר אחסון לפרוטוקולים כדי ליצור את קוד ה-gRPC של ה-boilerplate.
קודם יוצרים את ספריית העבודה של ה-codelab ומנווטים אליה באמצעות הפקודה cd:
mkdir streaming-grpc-rust-getting-started && cd streaming-grpc-rust-getting-started
מורידים ומחלצים את ה-Codelab:
curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/2026.tar.gz \
| tar xvz --strip-components=4 \
grpc-codelabs-2026/codelabs/grpc-rust-streaming/start_here
אפשרות אחרת היא להוריד את קובץ ה- .zip שמכיל רק את ספריית ה-codelab ולבטל את הדחיסה שלו באופן ידני.
קוד המקור המלא זמין ב-GitHub אם אתם רוצים לדלג על הקלדת ההטמעה.
3. הגדרת הודעות ושירותים
הצעד הראשון הוא להגדיר את שירות ה-gRPC של האפליקציה, את שיטות ה-RPC שלה ואת סוגי הודעות הבקשה והתגובה שלה באמצעות Protocol Buffers (מאגרי פרוטוקולים). השירות שלך יספק:
- שיטות RPC הנקראות
ListFeatures,RecordRouteו-RouteChatשהשרת מיישם והלקוח קורא להן. - סוגי ההודעות
Point,Feature,Rectangle,RouteNoteו-RouteSummary, שהן מבני נתונים שמועברים בין הלקוח לשרת כשמפעילים את השיטות שלמעלה.
כל השיטות האלה של RPC וסוגי ההודעות שלהן מוגדרות בקובץ proto/routeguide.proto של קוד המקור שסיפקתם.
Protocol Buffers ידועים בדרך כלל כ-protobufs. מידע נוסף על המינוח של gRPC זמין במאמר מושגים מרכזיים, ארכיטקטורה ומחזור חיים של gRPC.
הגדרת סוגי הודעות
קודם נגדיר את ההודעות שישמשו את קריאות ה-RPC שלנו. בקובץ proto/routeguide.proto של קוד המקור, מגדירים קודם את סוג ההודעה Point. הסמל Point מייצג זוג קואורדינטות של קו רוחב וקו אורך במפה. ב-codelab הזה, משתמשים במספרים שלמים לקואורדינטות:
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. ההודעה הזו מתקבלת בתגובה ל-RPC RecordRoute, שמוסבר בקטע הבא. הוא מכיל את מספר הנקודות הבודדות שהתקבלו, את מספר התכונות שזוהו ואת המרחק הכולל שעבר, כסכום מצטבר של המרחק בין כל נקודה.
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, נגדיר:
ListFeatures
מקבל את Features שזמינים בתוך Rectangle הנתון. התוצאות מועברות בסטרימינג ולא מוחזרות בבת אחת (למשל בהודעת תגובה עם שדה חוזר), כי המלבן עשוי לכסות שטח גדול ולהכיל מספר עצום של תכונות.
סוג ה-RPC המתאים במקרה הזה הוא RPC של סטרימינג בצד השרת: הלקוח שולח בקשה לשרת ומקבל סטרימינג כדי לקרוא רצף של הודעות בחזרה. הלקוח קורא מהזרם המוחזר עד שאין יותר הודעות. כפי שאפשר לראות בדוגמה שלנו, כדי לציין שיטת סטרימינג בצד השרת, צריך להציב את מילת המפתח stream לפני סוג התגובה.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
RecordRoute
מקבל זרם של Points במסלול שמוגדר למעבר, ומחזיר RouteSummary כשהמעבר מסתיים.
במקרה הזה, נראה ש-RPC של סטרימינג בצד הלקוח מתאים: הלקוח כותב רצף של הודעות ושולח אותן לשרת, שוב באמצעות סטרימינג שסופק. אחרי שהלקוח מסיים לכתוב את ההודעות, הוא מחכה שהשרת יקרא את כולן ויחזיר את התגובה שלו. כדי לציין שיטת סטרימינג בצד הלקוח, צריך להוסיף את מילת המפתח stream לפני סוג הבקשה.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
RouteChat
מקבלת זרם של RouteNotes שנשלחים בזמן שמתבצעת נסיעה במסלול, תוך כדי קבלת RouteNotes אחרים (למשל ממשתמשים אחרים).
זה בדיוק סוג השימוש בהעברת נתונים דו-כיוונית. ב-RPC של סטרימינג דו-כיווני, שני הצדדים שולחים רצף של הודעות באמצעות סטרימינג לקריאה ולכתיבה. שני הזרמים פועלים באופן עצמאי, כך שהלקוחות והשרתים יכולים לקרוא ולכתוב בכל סדר שרוצים.
לדוגמה, השרת יכול לחכות לקבלת כל ההודעות מהלקוח לפני שהוא כותב את התשובות שלו, או שהוא יכול לקרוא הודעה ואז לכתוב הודעה, או שילוב אחר של קריאות וכתיבות.
הסדר של ההודעות בכל זרם נשמר. כדי לציין את סוג ה-method הזה, צריך להוסיף את מילת המפתח stream לפני הבקשה ולפני התשובה.
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
4. יצירת קוד הלקוח והשרת
כבר שלחנו לך את הקוד שנוצר מקובץ .proto בספרייה generated/, כולל כל התוספות שביצעת למעלה. עם זאת, אנחנו רוצים להסביר לך איך פועל יצירת הקוד.
קובץ .proto שלנו מתאר את כל המבנים והפונקציות שבהם משתמשים לקוח או שרת. אנחנו משתמשים בסקריפט build של Cargo (build.rs) יחד עם תיבת grpc-protobuf-build כדי ליצור את הקוד הזה באופן אוטומטי.
ב-Cargo.toml כבר הוספנו את grpc-protobuf-build כתלות ב-build.
ב-build.rs, מגדירים את grpc_protobuf_build::CodeGen כך שיקמפל את proto/routeguide.proto לספרייה generated/. השורות העיקריות הן:
grpc_protobuf_build::CodeGen::new()
.include("proto")
.input("routeguide.proto")
.output_dir("generated")
.compile()
.unwrap();
הפקודה הזו מפעילה את יצירת הקוד של תיבת grpc_protobuf_build, ומעבירה לה את routeguide.proto. הוספנו את זה לקוד כדי שהפעולה תתבצע רק כשמועבר feature flag, כך שההגדרה תתבצע מחדש רק כשרוצים בכך. אין צורך להריץ את הפקודה הזו עכשיו, כי כבר יצרנו בשבילך את הקוד.
cargo build --bin routeguide-server --features regenerate_proto
כשמריצים את הפקודה cargo build, build.rs מהדר את ההגדרות של מאגר אחסון לפרוטוקולים בספרייה generate/, כולל:
- הגדרות של מבנה לסוגי ההודעות
Pointו-Feature. - מאפיין של שירות Tonic שנצטרך להטמיע בשרת:
route_guide_server::RouteGuide. - סוג לקוח gRPC-Rust שנשתמש בו כדי לקרוא לשרת:
route_guide_client::RouteGuideClient<T>.
מידע נוסף זמין במדריך בנושא protoc-gen-rust-grpc.
בשלב הבא, נטמיע את שיטות השירות בשרת.
5. הטמעה של השירות
קודם נראה איך יוצרים RouteGuide שרת. יש שני חלקים בתהליך שבו שירות RouteGuide מבצע את העבודה שלו:
- הטמעה של ממשק השירות שנוצר מהגדרת השירות שלנו: ביצוע ה "עבודה" בפועל של השירות שלנו.
- הפעלת שרת gRPC להאזנה לבקשות מלקוחות ולשליחתן להטמעה הנכונה של השיטה.
ב-src/server/server.rs, אפשר להשתמש בפקודת המאקרו include_generated_proto! של gRPC כדי להוסיף את הקוד שנוצר להיקף, ולייבא את המאפיין 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
};
אנחנו יכולים להתחיל בהגדרת struct שייצג את השירות שלנו. נכון לעכשיו, אפשר לעשות את זה ב-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
נתחיל עם ListFeatures. זהו RPC של סטרימינג מצד השרת (הלקוח ישלח הודעה אחת, השרת יגיב עם הרבה הודעות), ולכן אנחנו צריכים לשלוח בחזרה כמה Features ללקוח.
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
עכשיו נסתכל על משהו קצת יותר מסובך: שיטת הסטרימינג בצד הלקוח 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 של סטרימינג דו-כיווני: RouteChat
לבסוף, נבחן את ה-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. למרות שכל צד תמיד יקבל את ההודעות של הצד השני בסדר שבו הן נכתבו, גם הלקוח וגם השרת יכולים לקרוא ולכתוב בכל סדר – הזרמים פועלים באופן עצמאי לחלוטין.
אנחנו יוצרים את זרם הפלט באמצעות 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(), שלב אחר שלב:
- מציינים את היציאה שבה רוצים להשתמש כדי להאזין לבקשות של לקוחות
- יצירת
RouteGuideServiceעם תכונות שנטענו - יוצרים מופע של שרת gRPC באמצעות
RouteGuideServer::new()באמצעות השירות שיצרנו. - רושמים את ההטמעה של השירות בשרת gRPC.
- מתקשרים אל
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 לסטרימינג הן אסינכרוניות ולא חוסמות, והן משתמשות בתחביר async/await של Rust ובסטרימינג של Tokio.
RPC של סטרימינג בצד השרת: PrintFeatures
ב-RPC של סטרימינג מהשרת, הלקוח שולח הודעת בקשה אחת לשרת ומקבל בחזרה סטרימינג של הודעות תגובה. כאן אפשר לראות איפה ב-client.rs אנחנו קוראים לשיטת הסטרימינג בצד השרת list_features() (שמתאימה להצהרת ה-RPC ListFeatures שנמצאת בפרוטו שלנו). השרת ישלח בחזרה זרם של הודעות Feature:
async fn print_features(client: &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(rectangle).await;
while let Some(feature) = stream.recv().await {
println!(
"FEATURE: Name = \"{}\", Lat = {}, Lon = {}",
feature.name(),
feature.location().latitude(),
feature.location().longitude()
);
}
let status = stream.status().await;
assert!(status.is_ok(), "{:?}", status);
Ok(())
}
RPC של סטרימינג מצד הלקוח: RecordRoute
כשמשתמשים בסטרימינג בצד הלקוח, הלקוח פותח סטרימינג לשרת ושולח רצף של הודעות. הוא יקבל הודעת תשובה אחת כשהשידור יסתיים.
בדוגמה הזו, אנחנו יוזמים את הקריאה באמצעות client.record_route().await, שולחים כמה קואורדינטות שנוצרו Point אחת אחרי השנייה בסטרימינג באמצעות stream.send(point).await, ואז סוגרים את הסטרימינג באמצעות stream.close_and_recv().await כדי לקבל את ההודעה של השרת היחיד RouteSummary.
async fn run_record_route(client: &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 mut stream = client.record_route().await;
for point in &points {
if stream.send(point).await.is_err() {
break;
}
}
match stream.close_and_recv().await {
Ok(response) => {
println!(
"SUMMARY: Feature Count = {}, Distance = {}",
response.feature_count(),
response.distance()
);
}
Err(e) => println!("something went wrong: {e:?}"),
}
Ok(())
}
RPC של סטרימינג דו-כיווני: RouteChat
לבסוף, נבחן את ה-RPC של הסטרימינג הדו-כיווני RouteChat(). במקרה הזה, גם הלקוח וגם השרת יעבירו סדרת הודעות הלוך ושוב. אנחנו יוצרים משימה tokio כדי לשלוח הודעות לשרת באופן רציף באמצעות tx.send(note).await.is_err(). במקביל, rx.recv().await מאזין להודעות תגובה מהשרת ומדפיס אותן כשהן מגיעות.
async fn run_route_chat(client: &RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
let (mut tx, mut rx) = client.route_chat().await;
let start = time::Instant::now();
tokio::spawn(async move {
let mut interval = time::interval(Duration::from_millis(50));
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_millis() as i32,
longitude: -746188906,
}),
message: format!("at {elapsed:?}"),
});
if tx.send(note).await.is_err() {
return;
}
}
tx.close();
});
while let Some(note) = rx.recv().await {
println!(
"Note: Latitude = {}, Longitude = {}, Message = \"{}\"",
note.location().latitude(),
note.location().longitude(),
note.message()
);
}
let status = rx.status().await;
assert!(status.is_ok(), "{:?}", status);
Ok(())
}
למרות שכל צד תמיד יקבל את ההודעות של הצד השני בסדר שבו הן נכתבו, גם הלקוח וגם השרת יכולים לקרוא ולכתוב בכל סדר – הזרמים פועלים באופן עצמאי לחלוטין.
יצירה והעברה של לקוח
כדי להפעיל methods של שירות, קודם צריך ליצור ערוץ לתקשורת עם השרת. כדי ליצור את הקישור הזה, קודם יוצרים נקודת קצה, מתחברים לנקודת הקצה הזו ומעבירים את הערוץ שנוצר כשמתחברים אל RouteGuideClient::new() באופן הבא:
// Create channel to connect to server
let channel = Channel::builder(
"dns:///[::1]:10000",
Arc::new(LocalChannelCredentials::new()),
)
.build();
// Create a new client
let client = RouteGuideClient::new(channel);
אחרי שיוצרים את הלקוח הזה, אפשר להפעיל את השיטות שכתבנו למעלה ולהעביר אליהן את הלקוח. אנחנו מוסיפים את כל הקוד הזה ל-main(), שמשתמש בסביבת זמן ריצה אסינכרונית של Tokio. הנה הקוד המלא:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create channel to connect to server
let channel = Channel::builder(
"dns:///[::1]:10000",
Arc::new(LocalChannelCredentials::new()),
)
.build();
// Create a new client
let client = RouteGuideClient::new(channel);
println!("\n*** SERVER STREAMING ***");
print_features(&client).await?;
println!("\n*** CLIENT STREAMING ***");
run_record_route(&client).await?;
println!("\n*** BIDIRECTIONAL STREAMING ***");
run_route_chat(&client).await?;
Ok(())
}
7. רוצה לנסות?
כדי להריץ את הלקוח והשרת, צריך קודם לוודא ששני יעדי הקובץ הבינארי מוגדרים ב-Cargo.toml:
[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"
[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"
לאחר מכן, מריצים את הפקודות הבאות מספריית העבודה:
- מריצים את השרת במסוף אחד:
cargo run --bin routeguide-server
- מריצים את הלקוח ממסוף אחר:
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. המאמרים הבאים
- כדאי לעיין במאגר הרשמי של gRPC-Rust.
- מידע נוסף על ארכיטקטורת gRPC זמין במאמר מושגים מרכזיים.
- אפשר לעיין במסמכי התיעוד של gRPC-Rust באתר gRPC.io.
9. שותפים ביצירת ה-Codelab הזה
- קאתי ז'או
- Arvind Bright
- נתנאל פורד