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 ועוברים אליה:
mkdir grpc-rust-getting-started && cd 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-getting-started/start_here
אפשרות אחרת היא להוריד את קובץ ה- .zip שמכיל רק את ספריית ה-codelab ולבטל את הדחיסה שלו באופן ידני.
קוד המקור המלא זמין ב-GitHub אם אתם רוצים לדלג על הקלדת ההטמעה.
3. הגדרת השירות
השלב הראשון הוא להגדיר את שירות gRPC של האפליקציה, את שיטת ה-RPC ואת סוגי ההודעות של הבקשה והתגובה באמצעות Protocol Buffers. השירות שלכם יספק:
- שיטת RPC שנקראת
GetFeature
, שהשרת מטמיע והלקוח מפעיל. - סוגי ההודעות
Point
ו-Feature
שהן מבני נתונים שמועברים בין הלקוח לשרת כשמשתמשים בשיטהGetFeature
. הלקוח מספק קואורדינטות של מפה כ-Point
בבקשתGetFeature
שלו לשרת, והשרת משיב עםFeature
תואם שמתאר את מה שנמצא בקואורדינטות האלה.
שיטת ה-RPC הזו וסוגי ההודעות שלה יוגדרו בקובץ proto/route_guide.proto
של קוד המקור שסופק.
פרוטוקול Buffers ידוע בדרך כלל בשם protobufs. מידע נוסף על המינוח של gRPC זמין במאמר מושגי ליבה, ארכיטקטורה ומחזור חיים של gRPC.
שיטת השירות
קודם נגדיר את שיטות השירות ואז נגדיר את סוגי ההודעות Point
ו-Feature
. לקובץ proto/routeguide.proto
יש מבנה service
בשם RouteGuide
שמגדיר שיטה אחת או יותר שסופקו על ידי השירות של האפליקציה.
מוסיפים את השיטה rpc
GetFeature
בתוך ההגדרה של RouteGuide
. כמו שהוסבר קודם, השיטה הזו מחפשת את השם או הכתובת של מיקום לפי קבוצה נתונה של קואורדינטות, ולכן הפונקציה GetFeature
מחזירה את הערך Feature
עבור Point
נתון:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
זו שיטת RPC אוניטרית: RPC פשוט שבו הלקוח שולח בקשה לשרת ומחכה לתגובה, בדיוק כמו קריאה לפונקציה מקומית.
סוגי הודעות
בקובץ proto/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;
}
4. יצירת קוד הלקוח והשרת
כבר נתנו לך את הקוד שנוצר מהקובץ .proto
בספרייה שנוצרה.
כמו בכל פרויקט, אנחנו צריכים לחשוב על התלות שנדרשת לקוד שלנו. בפרויקטים של Rust, יחסי התלות יהיו ב-Cargo.toml
. כבר ציינו את התלויות הנדרשות בקובץ Cargo.toml
.
אם אתם רוצים ללמוד איך ליצור קוד מקובץ .proto
בעצמכם, תוכלו לעיין בהוראות האלה.
הקוד שנוצר מכיל:
- הגדרות של מבנים לסוגי ההודעות
Point
ו-Feature
. - מאפיין שירות שנצטרך להטמיע:
route_guide_server::RouteGuide
. - סוג הלקוח שבו נשתמש כדי לקרוא לשרת:
route_guide_client::RouteGuideClient<T>
.
בשלב הבא, נטמיע את השיטה GetFeature
בצד השרת, כדי שכשהלקוח ישלח בקשה, השרת יוכל להשיב עם תשובה.
5. הטמעה של השירות
ב-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,
};
אפשר להתחיל בהגדרת מבנה נתונים (struct) לייצוג השירות. אפשר לעשות את זה ב-src/server/server.rs
לעת עתה:
#[derive(Debug)]
pub struct RouteGuideService {
features: Vec<Feature>,
}
עכשיו צריך להטמיע את מאפיין route_guide_server::RouteGuide
מהקוד שנוצר.
Unary RPC
השיטה RouteGuideService
מטמיעה את כל שיטות השירות שלנו. הפונקציה get_feature
בצד השרת היא המקום שבו מתבצעת העבודה העיקרית: היא מקבלת הודעת Point
מהלקוח ומחזירה בהודעת Feature
את פרטי המיקום התואמים מתוך רשימה של מקומות מוכרים. הנה ההטמעה של הפונקציה ב-src/server/server.rs
:
#[tonic::async_trait]
impl RouteGuide for RouteGuideService {
async fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> {
println!("GetFeature = {:?}", request);
let requested_point = request.get_ref();
for feature in self.features.iter() {
if feature.location().latitude() == requested_point.latitude() {
if feature.location().longitude() == requested_point.longitude(){
return Ok(Response::new(feature.clone()))
};
};
}
Ok(Response::new(Feature::default()))
}
}
בשיטה, מאכלסים אובייקט Feature
במידע המתאים ל-Point
הנתון, ואז מחזירים אותו.
אחרי שמטמיעים את השיטה הזו, צריך גם להפעיל שרת 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
עם תכונות שנטענו על ידי הפעלת פונקציית העזרload()
- יוצרים מופע של שרת gRPC באמצעות
RouteGuideServer::new()
באמצעות השירות שיצרנו. - רושמים את הטמעת השירות בשרת gRPC.
- מתקשרים אל
serve()
בשרת עם פרטי היציאה שלנו כדי לבצע המתנה חוסמת עד שהתהליך יופסק.
6. יצירת הלקוח
בקטע הזה נראה איך ליצור לקוח Rust לשירות RouteGuide ב-src/client/client.rs
.
כמו שעשינו ב-src/server/server.rs
, אפשר להוסיף את הקוד שנוצר להיקף באמצעות מאקרו include_generated_code!
של gRPC ולייבא את הסוג RouteGuideClient
.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
use grpc_pb::{
route_guide_client::RouteGuideClient,
Point,
};
הפעלת שיטות של שירות
ב-gRPC-Rust, קריאות RPC פועלות במצב חסימה/סינכרוני, כלומר קריאת ה-RPC מחכה שהשרת יגיב, ותחזיר תגובה או שגיאה.
כדי להפעיל שיטות שירות, קודם צריך ליצור ערוץ לתקשורת עם השרת. כדי ליצור את הקישור הזה, קודם יוצרים נקודת קצה, מתחברים לנקודת הקצה הזו ומעבירים את הערוץ שנוצר כשמתחברים אל 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(())
}
בפונקציה הזו, כשיוצרים את הלקוח, עוטפים את הערוץ הכללי שנוצר למעלה ב-stub של הקוד שנוצר ומיישם את השיטות הספציפיות שמוגדרות בשירות .proto.
Simple RPC
הפעלת ה-RPC הפשוט GetFeature
היא כמעט פשוטה כמו הפעלת method מקומי. תעשה את זה בmain()
.
println!("*** SIMPLE RPC ***");
let point = proto!(Point{
latitude: 409_146_138,
longitude: -746_188_906
});
let response = client
.get_feature(Request::new(point))
.await?.into_inner();
Ok(())
כפי שאפשר לראות, אנחנו קוראים לשיטה ב-stub שקיבלנו קודם. בפרמטרים של השיטה אנחנו יוצרים ומאכלסים אובייקט של פרוטוקול בקשה (במקרה שלנו Point
). אם הקריאה לא מחזירה שגיאה, אפשר לקרוא את פרטי התגובה מהשרת מתוך ערך ההחזרה הראשון.
println!("Response = Name = \"{}\", Latitude = {}, Longitude = {}",
response.name(),
response.location().latitude(),
response.location().longitude());
בסך הכול, הפונקציה 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!("*** SIMPLE RPC ***");
let point = proto!(Point{
latitude: 409_146_138,
longitude: -746_188_906
});
let response = client
.get_feature(Request::new(point))
.await?.into_inner();
println!("Response = Name = \"{}\", Latitude = {}, Longitude = {}",
response.name(),
response.location().latitude(),
response.location().longitude());
Ok(())
}
7. רוצה לנסות?
קודם כול, כדי להריץ את הלקוח והשרת, נוסיף אותם כיעדים בינאריים ל-crate. אנחנו צריכים לערוך את Cargo.toml
בהתאם ולהוסיף את הפרטים הבאים:
[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"
[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"
לאחר מכן, מריצים את הפקודות הבאות מספריית העבודה:
- מריצים את השרת במסוף אחד:
RUSTFLAGS="-Awarnings" cargo run --bin routeguide-server
- מריצים את הלקוח ממסוף אחר:
RUSTFLAGS="-Awarnings" cargo run --bin routeguide-client
יוצג פלט כמו זה, בלי חותמות זמן כדי שיהיה ברור:
*** SIMPLE RPC *** FEATURE: Name = "Berkshire Valley Management Area Trail, Jefferson, NJ, USA", Lat = 409146138, Lon = -746188906
8. המאמרים הבאים
- מידע על gRPC זמין במאמרים מבוא ל-gRPC ומושגי ליבה.
- עוברים על המדריך למתחילים