1. مقدمه
در این کد لبه، شما از gRPC-Rust برای ایجاد یک کلاینت و سرور استفاده خواهید کرد که پایه و اساس برنامه نقشه برداری مسیر نوشته شده در Rust را تشکیل می دهد.
در پایان آموزش، یک کلاینت خواهید داشت که با استفاده از gRPC به یک سرور راه دور متصل می شود تا نام یا آدرس پستی آنچه در مختصات خاصی روی نقشه قرار دارد را دریافت کند. یک برنامه کاربردی کاملاً پیشرفته ممکن است از این طراحی سرویس گیرنده-سرور برای برشمردن یا خلاصه کردن نقاط مورد علاقه در طول مسیر استفاده کند.
این سرویس در یک فایل Protocol Buffers تعریف شده است که از آن برای تولید کد boilerplate برای کلاینت و سرور استفاده می شود تا بتوانند با یکدیگر ارتباط برقرار کنند و در زمان و تلاش شما در اجرای آن عملکرد صرفه جویی شود.
این کد تولید شده نه تنها از پیچیدگی های ارتباط بین سرور و کلاینت، بلکه سریال سازی و سریال سازی داده ها نیز مراقبت می کند.
چیزی که یاد خواهید گرفت
- نحوه استفاده از بافرهای پروتکل برای تعریف API سرویس.
- نحوه ساخت یک کلاینت و سرور مبتنی بر gRPC از تعریف بافرهای پروتکل با استفاده از تولید کد خودکار.
- درک ارتباط مشتری-سرور با gRPC.
این کد لبه برای توسعه دهندگان Rust که تازه به gRPC می پردازند یا به دنبال یک تجدید کننده gRPC هستند یا هر فرد دیگری که علاقه مند به ساختن سیستم های توزیع شده است، طراحی شده است. هیچ تجربه قبلی gRPC مورد نیاز نیست.
2. قبل از شروع
پیش نیازها
مطمئن شوید که موارد زیر را نصب کرده اید:
- شورای همکاری خلیج فارس دستورالعمل های اینجا را دنبال کنید
- Rust نسخه 1.89.0. دستورالعمل های نصب را در اینجا دنبال کنید.
کد را دریافت کنید
برای اینکه مجبور نباشید به طور کامل از ابتدا شروع کنید، این کد لبه داربستی از کد منبع برنامه را برای شما فراهم می کند تا آن را تکمیل کنید. مراحل زیر به شما نشان می دهد که چگونه برنامه را تکمیل کنید، از جمله استفاده از افزونه های کامپایلر بافر پروتکل برای تولید کد gRPC دیگ بخار.
ابتدا پوشه کاری codelab و cd را در آن ایجاد کنید:
mkdir grpc-rust-getting-started && cd 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-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
کد منبع ارائه شده تعریف می شوند.
بافرهای پروتکل معمولاً به عنوان پروتوباف شناخته می شوند. برای اطلاعات بیشتر در مورد اصطلاحات 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
نشان دهنده یک جفت مختصات طول و عرض جغرافیایی بر روی نقشه است. برای این کد، از اعداد صحیح برای مختصات استفاده کنید:
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!
ماکرو و ویژگی RouteGuide
و Point
را وارد کنید.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
pub use grpc_pb::{
route_guide_server::{RouteGuideServer, RouteGuide},
Point, Feature,
};
میتوانیم با تعریف ساختاری برای نشان دادن سرویس خود شروع کنیم، فعلاً میتوانیم این کار را در 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()
اتفاق می افتد، گام به گام است:
- پورتی را که می خواهیم برای گوش دادن به درخواست های مشتری استفاده کنیم را مشخص کنید
- با فراخوانی تابع helper
load()
یکRouteGuideService
با ویژگی های بارگذاری شده ایجاد کنید. - یک نمونه از سرور gRPC را با استفاده از
RouteGuideServer::new()
با استفاده از سرویسی که ایجاد کردیم ایجاد کنید. - اجرای سرویس ما را با سرور gRPC ثبت کنید.
-
serve()
روی سرور با جزئیات پورت ما فراخوانی کنید تا منتظر بمانید تا فرآیند کشته شود.
6. مشتری ایجاد کنید
در این بخش، ما به ایجاد یک مشتری Rust برای سرویس RouteGuide در src/client/client.rs
نگاه خواهیم کرد.
همانطور که در src/server/server.rs
انجام دادیم، میتوانیم کد تولید شده را از طریق include_generated_code!
ماکرو و نوع 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(())
}
در این تابع، هنگامی که مشتری را ایجاد می کنیم، کانال عمومی ایجاد شده در بالا را با کد خرد تولید شده که روش های خاص تعریف شده در سرویس .proto را پیاده سازی می کند، قرار می دهیم.
RPC ساده
فراخوانی ساده RPC GetFeature
تقریباً به آسانی فراخوانی یک متد محلی است. این را در 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(())
همانطور که می بینید، ما روشی را که در ابتدا دریافت کردیم، فراخوانی می کنیم. در پارامترهای روش خود، یک شی بافر پروتکل درخواست (در مورد ما 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. آن را امتحان کنید
ابتدا، برای اجرای Client و Server، بیایید آنها را به عنوان اهداف باینری به جعبه خود اضافه کنیم. ما باید 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 و Core بیاموزید
- از طریق آموزش اصول کار کنید