1. מבוא
ב-codelab הזה תשתמשו ב-gRPC-Rust כדי ליצור לקוח ושרת שמהווים את הבסיס לאפליקציית מיפוי מסלולים שנכתבה ב-Rust.
בסוף המדריך יהיה לכם לקוח שמתחבר לשרת מרוחק באמצעות gRPC כדי לקבל מידע על תכונות במסלול של לקוח, ליצור סיכום של המסלול של הלקוח ולהחליף מידע על המסלול, כמו עדכוני תנועה, עם השרת ועם לקוחות אחרים.
השירות מוגדר בקובץ Protocol Buffers, שישמש ליצירת קוד boilerplate ללקוח ולשרת, כדי שהם יוכלו לתקשר זה עם זה. כך תוכלו לחסוך זמן ומאמץ בהטמעת הפונקציונליות הזו.
הקוד שנוצר מטפל לא רק במורכבויות של התקשורת בין השרת ללקוח, אלא גם בסריאליזציה ובדה-סריאליזציה של הנתונים.
מה תלמדו
- איך משתמשים ב-Protocol Buffers כדי להגדיר API של שירות.
- איך ליצור לקוח ושרת מבוססי gRPC מהגדרה של Protocol Buffers באמצעות יצירת קוד אוטומטית.
- הבנה של תקשורת סטרימינג בין לקוח לשרת באמצעות gRPC.
ה-codelab הזה מיועד למפתחי Rust שחדשים ב-gRPC או שרוצים לרענן את הידע שלהם ב-gRPC, או לכל מי שמעוניין לבנות מערכות מבוזרות. לא נדרש ניסיון קודם ב-gRPC.
2. לפני שמתחילים
דרישות מוקדמות
ודאו שהתקנתם את הפריטים הבאים:
קבל את הקוד
כדי שלא תצטרכו להתחיל מאפס, ב-codelab הזה מופיע סקפולד של קוד המקור של האפליקציה שתוכלו להשלים. בשלבים הבאים מוסבר איך לסיים את האפליקציה, כולל שימוש בתוספים של קומפיילר פרוטוקול החוצץ כדי ליצור את קוד ה-boilerplate של gRPC.
קודם יוצרים את ספריית העבודה של ה-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/v1.tar.gz \ | tar xvz --strip-components=4 \ grpc-codelabs-1/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
של קוד המקור שסופק.
פרוטוקול Buffers ידוע בדרך כלל בשם protobufs. מידע נוסף על המינוח של gRPC זמין במאמר מושגי ליבה, ארכיטקטורה ומחזור חיים של gRPC.
הגדרת סוגי הודעות
קודם נגדיר את ההודעות שישמשו את קריאות ה-RPC שלנו. בקובץ routeguide/route_guide.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
ההודעה הזו מתקבלת בתגובה ל-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, נגדיר:
ListFeatures
מקבל את Feature
s שזמינים בתוך Rectangle
שצוין. התוצאות מועברות בסטרימינג ולא מוחזרות בבת אחת (לדוגמה, בהודעת תגובה עם שדה חוזר), כי יכול להיות שהמלבן יכסה אזור גדול ויכיל מספר עצום של תכונות.
סוג ה-RPC המתאים כאן הוא RPC של סטרימינג בצד השרת: הלקוח שולח בקשה לשרת ומקבל סטרימינג כדי לקרוא רצף של הודעות בחזרה. הלקוח קורא מהזרם שהוחזר עד שאין יותר הודעות. כפי שאפשר לראות בדוגמה שלנו, כדי לציין שיטת סטרימינג בצד השרת, צריך להציב את מילת המפתח stream
לפני סוג התגובה.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
RecordRoute
מקבל שידור של Point
s במסלול שמוגדר, ומחזיר RouteSummary
כשהמסלול מסתיים.
במקרה הזה, נראה ש-RPC של סטרימינג בצד הלקוח מתאים: הלקוח כותב רצף של הודעות ושולח אותן לשרת, שוב באמצעות סטרימינג שסופק. אחרי שהלקוח מסיים לכתוב את ההודעות, הוא מחכה שהשרת יקרא את כולן ויחזיר את התגובה שלו. כדי לציין שיטת סטרימינג בצד הלקוח, מציבים את מילת המפתח stream
לפני סוג הבקשה.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
RouteChat
מקבלת זרם של RouteNote
s שנשלחים בזמן שמתבצעת תנועה במסלול, תוך כדי קבלת RouteNote
s אחרים (למשל ממשתמשים אחרים).
זה בדיוק סוג השימוש שמתאים לסטרימינג דו-כיווני. ב-RPC דו-כיווני של סטרימינג, שני הצדדים שולחים רצף של הודעות באמצעות סטרימינג לקריאה ולכתיבה. שני הזרמים פועלים באופן עצמאי, כך שהלקוחות והשרתים יכולים לקרוא ולכתוב בכל סדר שרוצים.
לדוגמה, השרת יכול לחכות לקבלת כל ההודעות מהלקוח לפני שהוא כותב את התשובות שלו, או שהוא יכול לקרוא הודעה ואז לכתוב הודעה, או שילוב אחר של קריאות וכתיבות.
הסדר של ההודעות בכל פיד נשמר. כדי לציין את סוג ה-method הזה, צריך להוסיף את מילת המפתח 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!
של 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 בפירוט.
Server-side streaming RPC
נתחיל עם ListFeatures
. זוהי בקשת RPC להזרמת נתונים מצד השרת, ולכן אנחנו צריכים לשלוח בחזרה כמה Feature
s ללקוח שלנו.
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, הזרם תקין ואפשר להמשיך לקרוא ממנו.
Bidirectional streaming 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()
, שלב אחר שלב:
- מציינים את היציאה שבה רוצים להשתמש כדי להאזין לבקשות של לקוחות
- יצירת
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 פועלות במצב חסימה/סינכרוני, כלומר קריאת ה-RPC מחכה שהשרת יגיב, ותחזיר תגובה או שגיאה.
Server-side streaming 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
כדי לקרוא את התגובות של השרת. אנחנו משתמשים בשיטה ListFeaturesStream
של message()
כדי לקרוא שוב ושוב את התגובות של השרת לאובייקט של מאגר פרוטוקול תגובה (במקרה הזה 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(())
}
Bidirectional streaming 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. רוצה לנסות?
קודם כול, כדי להריץ את הלקוח והשרת, נוסיף אותם כיעדים בינאריים ל-crate. צריך לערוך את הקובץ 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
.
לאחר מכן, מריצים את הפקודות הבאות בספריות העבודה:
- מריצים את השרת במסוף אחד:
RUSTFLAGS="-Awarnings" cargo run --bin routeguide-server
- מריצים את הלקוח ממסוף אחר:
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. המאמרים הבאים
- מידע נוסף על gRPC זמין במאמרים מבוא ל-gRPC ומושגי ליבה.
- עוברים על המדריך למתחילים.