1. Giới thiệu
Trong bài thực hành này, bạn sẽ sử dụng gRPC-Rust để tạo ra một máy khách và máy chủ, tạo nên nền tảng của một ứng dụng lập bản đồ tuyến đường được viết bằng Rust.
Khi kết thúc hướng dẫn này, bạn sẽ có một máy khách kết nối với máy chủ từ xa bằng cách sử dụng phiên bản Rust chính thức của giao thức gRPC để lấy tên hoặc địa chỉ bưu chính của một vị trí tại tọa độ cụ thể trên bản đồ. Một ứng dụng hoàn chỉnh có thể sử dụng thiết kế máy chủ-máy khách này để liệt kê hoặc tóm tắt các điểm đáng chú ý dọc theo một tuyến đường.
Dịch vụ được định nghĩa trong một tệp Protocol Buffers, tệp này sẽ được sử dụng để tạo mã mẫu cho máy khách và máy chủ để chúng có thể giao tiếp với nhau, giúp bạn tiết kiệm thời gian và công sức trong việc triển khai chức năng đó.
Đoạn mã được tạo ra này không chỉ xử lý sự phức tạp trong giao tiếp giữa máy chủ và máy khách, mà còn cả việc tuần tự hóa và giải tuần tự hóa dữ liệu.
Kiến thức bạn sẽ học được
- Hướng dẫn cách sử dụng Protocol Buffers để định nghĩa API dịch vụ.
- Cách xây dựng một máy khách dựa trên gRPC từ định nghĩa Protocol Buffer bằng cách sử dụng công cụ tạo mã tự động.
- Hiểu biết về giao tiếp giữa máy khách và máy chủ bằng gRPC.
Bài thực hành này dành cho các nhà phát triển Rust mới làm quen với gRPC hoặc muốn ôn lại kiến thức về gRPC, hoặc bất kỳ ai khác quan tâm đến việc xây dựng hệ thống phân tán. Không yêu cầu kinh nghiệm sử dụng gRPC trước đó.
2. Trước khi bắt đầu
Điều kiện tiên quyết
Hãy đảm bảo bạn đã cài đặt những thứ sau:
- GCC. Làm theo hướng dẫn tại đây.
- Git: hướng dẫn cài đặt tại đây.
- Rust, phiên bản 1.88.0. Hãy làm theo hướng dẫn cài đặt tại đây.
Lấy mã
Để bạn không phải bắt đầu hoàn toàn từ đầu, bài hướng dẫn này cung cấp một khung sườn mã nguồn của ứng dụng để bạn hoàn thiện. Các bước sau đây sẽ hướng dẫn bạn cách hoàn thiện ứng dụng, bao gồm cả việc sử dụng các plugin trình biên dịch protocol buffer để tạo mã gRPC mẫu.
Đầu tiên, tạo thư mục làm việc của codelab và chuyển đến thư mục đó bằng lệnh `cd`:
mkdir grpc-rust-getting-started && cd grpc-rust-getting-started
Tải xuống và giải nén 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-getting-started/start_here
Ngoài ra, bạn cũng có thể tải xuống tệp .zip chỉ chứa thư mục codelab và giải nén thủ công.
Mã nguồn hoàn chỉnh có sẵn trên GitHub nếu bạn muốn bỏ qua việc tự gõ mã triển khai.
3. Xác định dịch vụ
Bước đầu tiên của bạn là xác định dịch vụ gRPC của ứng dụng, phương thức RPC của nó và các loại thông báo yêu cầu và phản hồi bằng cách sử dụng Protocol Buffers. Dịch vụ của bạn sẽ cung cấp:
- Một phương thức RPC có tên là
GetFeaturedo máy chủ triển khai và máy khách gọi. - Các kiểu thông báo
PointvàFeaturelà các cấu trúc dữ liệu được trao đổi giữa máy khách và máy chủ khi sử dụng phương thứcGetFeature. Máy khách cung cấp tọa độ bản đồ dưới dạngPointtrong yêu cầuGetFeaturecủa nó gửi đến máy chủ, và máy chủ trả lời bằng mộtFeaturetương ứng mô tả bất cứ thứ gì nằm ở tọa độ đó.
Phương thức RPC này và các kiểu thông báo của nó sẽ được định nghĩa đầy đủ trong tệp proto/routeguide.proto của mã nguồn được cung cấp.
Protocol Buffers thường được gọi là protobufs. Để biết thêm thông tin về thuật ngữ gRPC, hãy xem Các khái niệm cốt lõi, kiến trúc và vòng đời của gRPC.
Phương thức phục vụ
Trước tiên, chúng ta hãy định nghĩa các phương thức dịch vụ, sau đó định nghĩa các loại thông báo Point và Feature. Tệp proto/routeguide.proto có cấu trúc service tên là RouteGuide định nghĩa một hoặc nhiều phương thức do dịch vụ của ứng dụng cung cấp.
Thêm phương thức rpc GetFeature vào bên trong định nghĩa RouteGuide. Như đã giải thích trước đó, phương pháp này sẽ tra cứu tên hoặc địa chỉ của một vị trí từ một tập hợp tọa độ đã cho, vì vậy GetFeature sẽ trả về Feature cho một Point đã cho:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
Đây là một phương thức RPC đơn: một RPC đơn giản trong đó máy khách gửi yêu cầu đến máy chủ và chờ phản hồi trở lại, giống như một cuộc gọi hàm cục bộ.
Các loại tin nhắn
Trong tệp proto/routeguide.proto của mã nguồn, trước tiên hãy định nghĩa kiểu thông báo Point. Ký hiệu Point biểu thị một cặp tọa độ vĩ độ-kinh độ trên bản đồ. Đối với bài thực hành này, hãy sử dụng số nguyên cho tọa độ:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
Các số 1 và 2 là các số ID duy nhất cho mỗi trường trong cấu trúc message.
Tiếp theo, hãy định nghĩa kiểu thông báo Feature. Một Feature sử dụng một trường string cho tên hoặc địa chỉ bưu điện của một thứ gì đó tại vị trí được chỉ định bởi một Point:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
4. Tạo mã máy khách và máy chủ
Chúng tôi đã cung cấp cho bạn mã được tạo từ tệp .proto trong thư mục generated/, bao gồm tất cả các phần bổ sung mà bạn đã thực hiện ở trên. Tuy nhiên, chúng tôi muốn dành chút thời gian để giải thích cách thức hoạt động của quá trình tạo mã.
Tệp .proto của chúng tôi mô tả tất cả các cấu trúc và hàm mà máy khách hoặc máy chủ sử dụng. Chúng tôi sử dụng tập lệnh xây dựng Cargo (build.rs) cùng với crate grpc-protobuf-build để tự động tạo mã này.
Trong Cargo.toml chúng ta thêm grpc-protobuf-build dưới [build-dependencies].
Trong build.rs, chúng ta cấu hình grpc_protobuf_build::CodeGen để biên dịch proto/routeguide.proto vào thư mục generated/. Những điểm chính cần lưu ý như sau:
grpc_protobuf_build::CodeGen::new()
.include("proto")
.input("routeguide.proto")
.output_dir("generated")
.compile()
.unwrap();
Thao tác này gọi quá trình tạo mã của thùng grpc_protobuf_build, truyền routeguide.proto cho thùng này. Chúng tôi đã gói nội dung này trong một số mã để chỉ chạy khi một cờ tính năng được truyền, để nội dung này chỉ được tạo lại khi bạn muốn. Bạn không cần chạy mã này ngay bây giờ vì chúng tôi đã tạo mã cho bạn.
cargo build --bin routeguide-server --features regenerate_proto
Khi bạn chạy cargo build, build.rs sẽ biên dịch các định nghĩa vùng đệm giao thức thành thư mục generate/, bao gồm:
- Định nghĩa cấu trúc cho các loại thông báo
PointvàFeature. - Một đặc điểm dịch vụ Tonic mà chúng ta cần triển khai cho máy chủ:
route_guide_server::RouteGuide. - Một loại ứng dụng gRPC-Rust mà chúng ta sẽ dùng để gọi máy chủ:
route_guide_client::RouteGuideClient<T>.
Bạn có thể tham khảo hướng dẫn về protoc-gen-rust-grpc để biết thêm thông tin.
Tiếp theo, chúng ta sẽ triển khai các phương thức dịch vụ trên máy chủ.
5. Triển khai dịch vụ
Trong src/server/server.rs, chúng ta có thể đưa mã được tạo vào phạm vi thông qua macro include_generated_proto! của gRPC và nhập đặc điểm RouteGuide và Point.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
use grpc_pb::{
route_guide_server::{RouteGuideServer, RouteGuide},
Point, Feature,
};
Chúng ta có thể bắt đầu bằng cách xác định một cấu trúc để biểu thị dịch vụ của mình. Hiện tại, chúng ta có thể thực hiện việc này trên src/server/server.rs:
#[derive(Debug)]
pub struct RouteGuideService {
features: Vec<Feature>,
}
Giờ đây, chúng ta cần triển khai đặc điểm route_guide_server::RouteGuide từ mã đã tạo.
RPC đơn giản
RouteGuideService triển khai tất cả các phương thức dịch vụ của chúng tôi. Hàm get_feature ở phía máy chủ là nơi thực hiện công việc chính: hàm này nhận một thông báo Point từ máy khách và trả về thông tin vị trí tương ứng từ danh sách các địa điểm đã biết trong thông báo Feature. Dưới đây là cách triển khai hàm trong 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()))
}
}
Sau khi triển khai phương thức này, chúng ta cũng cần khởi động một máy chủ gRPC để máy khách có thể thực sự sử dụng dịch vụ của chúng ta. Thay thế main() bằng mã này.
#[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(())
}
Sau đây là những gì diễn ra trong main(), từng bước:
- Chỉ định cổng mà chúng ta muốn dùng để theo dõi các yêu cầu của máy khách
- Tạo một
RouteGuideServicevới các tính năng được tải sẵn bằng cách gọi hàm trợ giúpload() - Tạo một phiên bản của máy chủ gRPC bằng cách sử dụng
RouteGuideServer::new()với dịch vụ mà chúng ta đã tạo. - Đăng ký triển khai dịch vụ của chúng tôi với máy chủ gRPC.
- Gọi
serve()trên máy chủ với thông tin cổng của chúng ta để thực hiện chờ chặn cho đến khi tiến trình bị chấm dứt.
6. Tạo ứng dụng
Trong phần này, chúng ta sẽ xem xét việc tạo một ứng dụng Rust cho dịch vụ RouteGuide trong src/client/client.rs.
Như chúng ta đã làm ở src/server/server.rs, chúng ta có thể đưa mã được tạo vào phạm vi thông qua macro include_generated_proto! của gRPC và nhập kiểu RouteGuideClient.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
use grpc_pb::{
route_guide_client::RouteGuideClient,
Point,
};
Phương thức gọi điện
Trong gRPC-Rust, các RPC hoạt động bất đồng bộ và không chặn, sử dụng cú pháp async/await của Rust để chờ phản hồi từ máy chủ.
Để gọi các phương thức dịch vụ, trước tiên, chúng ta tạo một Channel bằng cách sử dụng Channel::builder(), chỉ định địa chỉ máy chủ (dns:///[::1]:10000) và thông tin đăng nhập kết nối (LocalChannelCredentials). Sau đó, chúng ta truyền kênh đến RouteGuideClient::new() để tạo thực thể cho ứng dụng khách:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new gRPC channel
let channel = Channel::builder(
"dns:///[::1]:10000",
Arc::new(LocalChannelCredentials::new()),
)
.build();
// Create a new client
let client = RouteGuideClient::new(channel);
}
Trong hàm này, RouteGuideClient::new() liên kết kênh chung với đoạn mã máy khách được tạo ra, đoạn mã này triển khai các phương thức được định nghĩa trong định nghĩa dịch vụ .proto của chúng ta.
RPC đơn giản
Việc gọi RPC đơn giản GetFeature cũng dễ dàng như gọi một phương thức cục bộ. Thêm nội dung này vào main():
println!("*** SIMPLE RPC ***");
let point = proto!(Point {
latitude: 409_146_138,
longitude: -746_188_906,
});
let response = client
.get_feature(point)
.await
.expect("RPC error");
Trong gRPC-Rust, chúng ta sẽ truyền thông báo protobuf Point trực tiếp đến client.get_feature(point). Chờ đợi future trả về sẽ trả về phản hồi Feature trực tiếp mà không cần gọi thêm phương thức nào khác.
Tiếp theo, in các trường từ phản hồi:
println!(
"Response = Name = \"{}\", Latitude = {}, Longitude = {}",
response.name(),
response.location().latitude(),
response.location().longitude()
);
Nhìn chung, hàm main() của ứng dụng sẽ có dạng như sau:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Create a new gRPC channel
let channel = Channel::builder(
"dns:///[::1]:10000",
Arc::new(LocalChannelCredentials::new()),
)
.build();
// 2. Instantiate the generated RouteGuideClient
let client = RouteGuideClient::new(channel);
println!("*** SIMPLE RPC ***");
let point = proto!(Point {
latitude: 409_146_138,
longitude: -746_188_906,
});
let response = client
.get_feature(point)
.await
.expect("RPC error");
println!(
"Response = Name = \"{}\", Latitude = {}, Longitude = {}",
response.name(),
response.location().latitude(),
response.location().longitude()
);
Ok(())
}
7. Dùng thử
Để chạy ứng dụng và máy chủ, trước tiên, hãy xác minh rằng cả hai mục tiêu nhị phân đều được xác định trong Cargo.toml:
[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"
[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"
Tiếp theo, hãy thực thi các lệnh sau từ thư mục làm việc của chúng ta:
- Chạy máy chủ trong một cửa sổ terminal:
cargo run --bin routeguide-server
- Chạy ứng dụng từ một thiết bị đầu cuối khác:
cargo run --bin routeguide-client
Bạn sẽ thấy kết quả như sau, trong đó dấu thời gian đã bị bỏ qua để đảm bảo sự rõ ràng:
*** SIMPLE RPC ***
Response = Name = "Berkshire Valley Management Area Trail, Jefferson, NJ, USA", Latitude = 409146138, Longitude = -746188906
8. Bước tiếp theo
- Tiếp tục với lớp học lập trình Bắt đầu sử dụng gRPC-Rust (Truyền trực tuyến).
- Khám phá Kho lưu trữ gRPC-Rust chính thức.
- Tìm hiểu thêm về cấu trúc gRPC trong phần Các khái niệm cốt lõi.
- Xem tài liệu về gRPC-Rust trên gRPC.io.
9. Cộng tác viên của Lớp học lập trình này
- Cathy Zhao
- Lucio Franco
- Arvind Bright
- Nathaniel Ford