1. Giới thiệu
Trong lớp học lập trình này, bạn sẽ sử dụng gRPC-Rust để tạo một ứng dụng khách và máy chủ tạo thành 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 gRPC để lấy thông tin về các tính năng trên tuyến đường của máy khách, tạo bản tóm tắt tuyến đường của máy khách và trao đổi thông tin tuyến đường như cập nhật lưu lượng truy cập với máy chủ và các máy khách khác.
Dịch vụ này được xác định trong một tệp Protocol Buffers. Tệp này sẽ được dùng để tạo mã nguyên mẫu cho ứng dụng 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 khi triển khai chức năng đó.
Mã được tạo này không chỉ xử lý sự phức tạp của việc giao tiếp giữa máy chủ và ứng dụng mà còn xử lý việc chuyển đổi tuần tự và chuyển đổi không tuần tự dữ liệu.
Kiến thức bạn sẽ học được
- Cách sử dụng Protocol Buffers để xác định một API dịch vụ.
- Hướng dẫn cách xây dựng máy khách và máy chủ dựa trên gRPC từ định nghĩa Protocol Buffers bằng cách sử dụng công cụ tạo mã tự động.
- Hiểu rõ về giao tiếp truyền phát trực tiếp giữa máy khách và máy chủ bằng gRPC.
Lớp học lập trình này dành cho những nhà phát triển Rust mới làm quen với gRPC hoặc muốn tìm hiểu lại về gRPC, hoặc bất kỳ ai khác quan tâm đến việc xây dựng các hệ thống phân tán. Bạn không cần có kinh nghiệm sử dụng gRPC.
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 tất ứng dụng, bao gồm cả việc sử dụng trình bổ trợ trình biên dịch vùng đệm giao thức để tạo mã gRPC chung.
Đầu tiên, tạo thư mục làm việc của codelab và thêm cd vào đó:
mkdir streaming-grpc-rust-getting-started && cd streaming-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-streaming/start_here
Ngoài ra, bạn có thể tải tệp .zip chỉ chứa thư mục codelab xuống rồi giải nén theo cách thủ công.
Bạn có thể xem mã nguồn hoàn chỉnh trên GitHub nếu muốn bỏ qua bước nhập nội dung triển khai.
3. Xác định thông điệp và dịch vụ
Bước đầu tiên là xác định dịch vụ gRPC của ứng dụng, các phương thức RPC và các loại thông báo yêu cầu và phản hồi bằng Protocol Buffers. Dịch vụ của bạn sẽ cung cấp:
- Các phương thức RPC được gọi là
ListFeatures,RecordRoutevàRouteChatmà máy chủ triển khai và máy khách gọi. - Các loại thông báo
Point,Feature,Rectangle,RouteNotevàRouteSummarylà các cấu trúc dữ liệu được trao đổi giữa máy khách và máy chủ khi gọi các phương thức ở trên.
Tất cả các phương thức RPC này và các loại thông báo của phương thức sẽ được xác định 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.
Xác định các loại thông báo
Trước tiên, hãy xác định các thông báo sẽ được dùng bởi các RPC của chúng ta. Trong tệp proto/routeguide.proto của mã nguồn, trước tiên hãy xác định 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à số nhận dạng duy nhất cho từng trường trong cấu trúc message.
Tiếp theo, hãy xác định loại thông báo Feature. Feature sử dụng trường string cho tên hoặc địa chỉ bưu chính của một thứ gì đó tại một vị trí do Point chỉ định:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
Tiếp theo là thông báo Rectangle biểu thị một hình chữ nhật vĩ độ – kinh độ, được biểu thị dưới dạng hai điểm đối diện theo đường chéo "lo" và "hi".
message Rectangle {
// One corner of the rectangle.
Point lo = 1;
// The other corner of the rectangle.
Point hi = 2;
}
Ngoài ra, một thông báo RouteNote biểu thị một thông báo được gửi khi đang ở một vị trí nhất định.
message RouteNote {
// The location from which the message is sent.
Point location = 1;
// The message to be sent.
string message = 2;
}
Chúng tôi cũng sẽ yêu cầu bạn gửi một thông báo RouteSummary. Thông báo này được nhận để phản hồi một RPC RecordRoute. RPC này sẽ được giải thích trong phần tiếp theo. Tệp này chứa số điểm riêng lẻ nhận được, số lượng đối tượng được phát hiện và tổng khoảng cách đã đi được tính bằng tổng tích luỹ khoảng cách giữa mỗi điểm.
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;
}
Xác định các phương thức dịch vụ
Trước tiên, chúng ta hãy định nghĩa dịch vụ của mình, rồi sau đó mới định nghĩa các thông điệp. Để xác định một dịch vụ, bạn chỉ định một dịch vụ có tên trong tệp .proto. 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.
Xác định các phương thức RPC trong định nghĩa dịch vụ của bạn, chỉ định các loại yêu cầu và phản hồi của chúng. Trong phần này của lớp học lập trình, hãy xác định:
ListFeatures
Lấy các Feature có trong Rectangle đã cho. Kết quả được truyền trực tuyến thay vì trả về cùng một lúc (ví dụ: trong một thông báo phản hồi có trường lặp lại), vì hình chữ nhật có thể bao phủ một khu vực rộng lớn và chứa một số lượng lớn các đối tượng.
Một kiểu dữ liệu phù hợp cho RPC này làphía máy chủ Giao tiếp RPC theo luồng: máy khách gửi yêu cầu đến máy chủ và nhận lại một luồng dữ liệu để đọc một chuỗi các thông báo. Ứng dụng đọc từ luồng được trả về cho đến khi không còn thông báo nào nữa. Như bạn có thể thấy trong ví dụ của chúng tôi, bạn chỉ định một phương thức truyền phát trực tiếp phía máy chủ bằng cách đặt từ khoá stream trước loại phản hồi.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
RecordRoute
Chấp nhận một luồng Point trên một tuyến đường đang được đi qua, trả về một RouteSummary khi quá trình đi qua hoàn tất.
RPC truyền trực tuyến phía máy khách có vẻ phù hợp trong trường hợp này: máy khách ghi một chuỗi thông báo và gửi chúng đến máy chủ, một lần nữa sử dụng một luồng được cung cấp. Sau khi máy khách hoàn tất việc viết các tin nhắn, nó sẽ chờ máy chủ đọc hết tất cả và trả về phản hồi. Bạn chỉ định một phương thức truyền phát trực tiếp phía máy khách bằng cách đặt từ khoá stream trước loại yêu cầu.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
RouteChat
Chấp nhận một luồng các giá trị RouteNote được gửi trong khi đang duyệt một tuyến đường, đồng thời nhận các giá trị RouteNote khác (ví dụ: từ người dùng khác).
Đây chính xác là loại trường hợp sử dụng cho truyền phát trực tiếp hai chiều. Giao thức RPC truyền dữ liệu hai chiều cho phép cả hai phía gửi một chuỗi thông điệp bằng cách sử dụng luồng đọc-ghi. Hai luồng dữ liệu hoạt động độc lập, do đó máy khách và máy chủ có thể đọc và ghi theo bất kỳ thứ tự nào họ muốn.
Ví dụ: máy chủ có thể đợi nhận tất cả các thông báo của ứng dụng trước khi ghi phản hồi, hoặc có thể đọc một thông báo rồi ghi một thông báo, hoặc một số tổ hợp khác giữa đọc và ghi.
Thứ tự các thông báo trong mỗi luồng được giữ nguyên. Bạn chỉ định loại phương thức này bằng cách đặt từ khoá stream trước cả yêu cầu và phản hồi.
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
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ả những nội dung 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 hoạt động của tính năng 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ột máy khách hoặc máy chủ sử dụng. Chúng tôi sử dụng tập lệnh Cargo build (build.rs) cùng với thùng grpc-protobuf-build để tự động tạo mã này.
Trong Cargo.toml chúng tôi đã thêm grpc-protobuf-build làm phụ thuộc xây dựng.
Trong build.rs, chúng ta định cấu hình grpc_protobuf_build::CodeGen để biên dịch proto/routeguide.proto thành thư mục generated/. Sau đây là những dòng chính:
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ụ
Trước tiên, hãy xem cách chúng ta tạo một máy chủ RouteGuide. Có hai phần để dịch vụ RouteGuide thực hiện công việc của mình:
- Triển khai giao diện dịch vụ được tạo từ định nghĩa dịch vụ của chúng ta: thực hiện "công việc" thực tế của dịch vụ.
- Chạy một máy chủ gRPC để theo dõi các yêu cầu từ ứng dụng và gửi các yêu cầu đó đến phương thức triển khai phù hợp.
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");
}
pub use grpc_pb::{
route_guide_server::{RouteGuideServer, RouteGuide},
Point, Feature, Rectangle, RouteNote, RouteSummary
};
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. Chúng ta có thể làm điều này trên src/server/server.rs trước đã:
#[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.
Triển khai RouteGuide
Chúng ta cần triển khai giao diện RouteGuide đã tạo. Đây là cách triển khai. Mục này đã có trong mẫu.
#[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> {
...
}
}
Hãy cùng tìm hiểu chi tiết về từng cách triển khai RPC.
RPC truyền trực tuyến phía máy chủ: ListFeatures
Chúng ta hãy bắt đầu với ListFeatures. Đây là một RPC truyền trực tuyến phía máy chủ (ứng dụng sẽ gửi một thông báo, máy chủ sẽ phản hồi bằng nhiều thông báo), vì vậy, chúng ta cần gửi lại nhiều Feature cho ứng dụng của mình.
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)))
}
Như bạn có thể thấy, chúng ta nhận được một đối tượng yêu cầu (Rectangle mà ứng dụng muốn tìm Features). Lần này, chúng ta cần trả về một luồng giá trị. Chúng ta tạo một kênh và tạo ra một tác vụ không đồng bộ mới, trong đó chúng ta thực hiện một thao tác tra cứu, gửi các đối tượng đáp ứng các ràng buộc của chúng ta vào kênh. Nửa Stream của kênh được trả về cho người gọi, được bao bọc trong một tonic::Response.
RPC truyền trực tuyến phía máy khách: RecordRoute
Bây giờ chúng ta hãy xem xét một thứ phức tạp hơn một chút: phương thức truyền phát phía máy khách RecordRoute, trong đó chúng ta nhận được một luồng Points từ máy khách và trả về một RouteSummary duy nhất với thông tin về chuyến đi của họ. Thao tác này sẽ nhận một luồng làm dữ liệu đầu vào mà máy chủ có thể dùng để vừa đọc vừa ghi thông báo. Nó có thể lặp qua các thông báo của máy khách bằng phương thức next() của nó và trả về phản hồi duy nhất.
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))
}
Trong phần nội dung phương thức, chúng ta sử dụng phương thức next() của luồng để liên tục đọc các yêu cầu của ứng dụng khách vào một đối tượng yêu cầu (trong trường hợp này là Point) cho đến khi không còn thông báo nào nữa. Nếu giá trị này là None, tức là luồng vẫn hoạt động tốt và có thể tiếp tục đọc.
RPC truyền trực tuyến hai chiều: RouteChat
Cuối cùng, hãy xem RPC truyền trực tuyến hai chiều 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)))
}
Lần này, chúng ta sẽ nhận được một luồng mà, như trong ví dụ về truyền phát trực tiếp phía máy khách, có thể dùng để đọc và ghi tin nhắn. Tuy nhiên, lần này chúng ta trả về các giá trị thông qua luồng của phương thức trong khi máy khách vẫn đang ghi tin nhắn vào luồng tin nhắn của nó. Cú pháp để đọc và ghi ở đây rất giống với phương thức truyền phát trực tiếp của máy khách, ngoại trừ việc máy chủ trả về một RouteChatStream. Mặc dù mỗi bên sẽ luôn nhận được tin nhắn của bên kia theo thứ tự chúng được viết, nhưng cả máy khách và máy chủ đều có thể đọc và ghi theo bất kỳ thứ tự nào – các luồng hoạt động hoàn toàn độc lập.
Chúng ta tạo luồng đầu ra bằng cách sử dụng try_stream!, cho biết rằng luồng có thể trả về lỗi.
Khởi động máy chủ
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. Điền thông tin vào 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(())
}
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
RouteGuideServicecó các tính năng được tải vào - Tạo một phiên bản của máy chủ gRPC bằng
RouteGuideServer::new()bằng dịch vụ mà chúng ta đã tạo. - Đăng ký việc 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ủ bằng thông tin chi tiết về cổng của chúng tôi để thực hiện một lệnh chờ chặn cho đến khi quy trình bị huỷ.
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.
Trước tiên, hãy đưa mã đã tạo vào phạm vi.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
use grpc_pb::route_guide_client::RouteGuideClient;
use grpc_pb::{Point, Rectangle, RouteNote};
Phương thức gọi điện
Bây giờ, hãy xem cách chúng ta gọi các phương thức dịch vụ. Trong gRPC-Rust, các RPC truyền dữ liệu được thực hiện bất đồng bộ và không chặn, sử dụng cú pháp async/await của Rust và các luồng Tokio.
RPC truyền trực tuyến phía máy chủ: PrintFeatures
Trong các RPC truyền trực tuyến phía máy chủ, ứng dụng sẽ gửi một thông báo yêu cầu duy nhất đến máy chủ và nhận lại một luồng thông báo phản hồi. Đây là vị trí trong client.rs mà chúng ta gọi phương thức truyền phát trực tiếp phía máy chủ list_features() (tương ứng với khai báo rpc ListFeatures có trong proto của chúng ta). Máy chủ sẽ gửi lại một chuỗi các tin nhắn 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 truyền trực tuyến phía máy khách: RecordRoute
Khi chúng ta sử dụng tính năng truyền trực tuyến phía máy khách, máy khách sẽ mở một luồng đến máy chủ và gửi một chuỗi thông báo. Nó sẽ nhận được một thông báo phản hồi duy nhất khi luồng kết thúc.
Ở đây, chúng ta bắt đầu cuộc gọi bằng client.record_route().await, gửi lần lượt một số toạ độ Point đã tạo trên luồng bằng stream.send(point).await, rồi đóng luồng bằng stream.close_and_recv().await để nhận thông báo RouteSummary của một máy chủ.
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 truyền trực tuyến hai chiều: RouteChat
Cuối cùng, hãy xem RPC truyền trực tuyến hai chiều RouteChat(). Trong trường hợp này, cả máy khách và máy chủ sẽ truyền một chuỗi thông báo qua lại. Chúng ta tạo một tác vụ tokio để liên tục gửi thông báo đến máy chủ bằng tx.send(note).await.is_err(). Trong khi đó, rx.recv().await sẽ lắng nghe các thông báo phản hồi từ máy chủ và in các thông báo đó khi chúng đến.
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(())
}
Mặc dù mỗi bên sẽ luôn nhận được tin nhắn của bên kia theo thứ tự chúng được viết, nhưng cả máy khách và máy chủ đều có thể đọc và ghi theo bất kỳ thứ tự nào – các luồng hoạt động hoàn toàn độc lập.
Tạo và truyền ứng dụng khách
Để gọi các phương thức dịch vụ, trước tiên, chúng ta cần tạo một kênh để giao tiếp với máy chủ. Chúng ta tạo mục này bằng cách tạo một điểm cuối, kết nối với điểm cuối đó và truyền kênh được tạo khi kết nối với RouteGuideClient::new() như sau:
// 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);
Sau khi tạo ứng dụng này, chúng ta có thể gọi các phương thức mà chúng ta đã viết ở trên, truyền ứng dụng vào các phương thức đó. Chúng ta sẽ thêm tất cả mã này vào main(), đang sử dụng thời gian chạy không đồng bộ Tokio. Sau đây là mã đầy đủ:
#[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. 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"
Sau đó, hãy thực thi các lệnh sau từ thư mục đang hoạt động của chúng ta:
- Chạy máy chủ trong một dòng lệnh:
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:
*** 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. Bước tiếp theo
- 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
- Arvind Bright
- Nathaniel Ford