1. Einführung
In diesem Codelab erstellen Sie mit gRPC-Rust einen Client und einen Server, die die Grundlage für eine in Rust geschriebene Anwendung zur Routenplanung bilden.
Am Ende der Anleitung haben Sie einen Client, der über gRPC eine Verbindung zu einem Remote-Server herstellt, um Informationen zu Features auf der Route eines Clients abzurufen, eine Zusammenfassung der Route eines Clients zu erstellen und Routeninformationen wie Verkehrsmeldungen mit dem Server und anderen Clients auszutauschen.
Der Dienst wird in einer Protocol Buffers-Datei definiert, die verwendet wird, um Boilerplate-Code für den Client und den Server zu generieren, damit sie miteinander kommunizieren können. So sparen Sie Zeit und Aufwand bei der Implementierung dieser Funktion.
Dieser generierte Code kümmert sich nicht nur um die Komplexitäten der Kommunikation zwischen Server und Client, sondern auch um die Serialisierung und Deserialisierung von Daten.
Lerninhalte
- Verwendung von Protocol Buffers zum Definieren einer Dienst-API.
- Erstellen eines gRPC-basierten Clients und Servers aus einer Protocol Buffers-Definition mithilfe der automatischen Codegenerierung.
- Grundlagen der Streaming-Kommunikation zwischen Client und Server mit gRPC.
Dieses Codelab richtet sich an Rust-Entwickler, die neu in gRPC sind oder ihre Kenntnisse auffrischen möchten, sowie an alle anderen, die sich für die Entwicklung verteilter Systeme interessieren. Es sind keine Vorkenntnisse in gRPC erforderlich.
2. Hinweis
Vorbereitung
Achten Sie darauf, dass Sie Folgendes installiert haben:
- GCC. Eine Anleitung dazu finden Sie hier.
- Git: Eine Installationsanleitung finden Sie hier.
- Rust, Version 1.88.0. Eine Installationsanleitung finden Sie hier.
Code abrufen
Damit Sie nicht ganz von vorn beginnen müssen, enthält dieses Codelab ein Gerüst des Quellcodes der Anwendung, das Sie vervollständigen können. In den folgenden Schritten wird gezeigt, wie Sie die Anwendung fertigstellen, einschließlich der Verwendung der Protocol Buffer-Compiler-Plug-ins zum Generieren des Boilerplate-gRPC-Codes.
Erstellen Sie zuerst das Arbeitsverzeichnis für das Codelab und cd Sie zu diesem Verzeichnis:
mkdir streaming-grpc-rust-getting-started && cd streaming-grpc-rust-getting-started
Laden Sie das Codelab herunter und extrahieren Sie es:
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
Alternativ können Sie die ZIP-Datei herunterladen, die nur das Codelab-Verzeichnis enthält, und sie manuell entzippen.
Der vollständige Quellcode ist auf GitHub verfügbar, wenn Sie die Implementierung nicht eingeben möchten.
3. Nachrichten und Dienste definieren
Im ersten Schritt definieren Sie den gRPC-Dienst der Anwendung, die zugehörigen RPC-Methoden sowie die Nachrichten- und Antworttypen mithilfe von Protocol Buffers. Ihr Dienst bietet Folgendes:
- RPC-Methoden namens
ListFeatures,RecordRouteundRouteChat, die vom Server implementiert und vom Client aufgerufen werden. - Die Nachrichtentypen
Point,Feature,Rectangle,RouteNoteundRouteSummary, die Datenstrukturen sind, die beim Aufrufen der oben genannten Methoden zwischen Client und Server ausgetauscht werden.
Diese RPC-Methoden und die zugehörigen Nachrichtentypen werden alle in der Datei proto/routeguide.proto des bereitgestellten Quellcodes definiert.
Protocol Buffers werden häufig als Protobufs bezeichnet. Weitere Informationen zur gRPC-Terminologie finden Sie unter gRPC's Kernkonzepte, -Architektur und -Lebenszyklus.
Nachrichtentypen definieren
Definieren wir zuerst die Nachrichten, die von unseren RPCs verwendet werden. Definieren Sie in der Datei proto/routeguide.proto des Quellcodes zuerst den Nachrichtentyp Point. Ein Point stellt ein Breiten- und Längengrad-Koordinatenpaar auf einer Karte dar. Verwenden Sie für dieses Codelab Ganzzahlen für die Koordinaten:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
Die Zahlen 1 und 2 sind eindeutige ID-Nummern für die einzelnen Felder in der message-Struktur.
Definieren Sie als Nächstes den Nachrichtentyp Feature. Ein Feature verwendet ein string-Feld für den Namen oder die Postadresse von etwas an einem Ort, der durch einen Point angegeben wird:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
Als Nächstes eine Rectangle-Nachricht, die ein Breiten- und Längengrad-Rechteck darstellt, das als zwei diagonal gegenüberliegende Punkte „lo“ und „hi“ dargestellt wird.
message Rectangle {
// One corner of the rectangle.
Point lo = 1;
// The other corner of the rectangle.
Point hi = 2;
}
Außerdem eine RouteNote-Nachricht, die eine Nachricht darstellt, die an einem bestimmten Punkt gesendet wird.
message RouteNote {
// The location from which the message is sent.
Point location = 1;
// The message to be sent.
string message = 2;
}
Wir benötigen auch eine RouteSummary-Nachricht. Diese Nachricht wird als Antwort auf einen RecordRoute-RPC empfangen, der im nächsten Abschnitt erläutert wird. Sie enthält die Anzahl der empfangenen einzelnen Punkte, die Anzahl der erkannten Features und die zurückgelegte Gesamtstrecke als kumulative Summe der Entfernung zwischen den einzelnen Punkten.
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;
}
Dienstmethoden definieren
Definieren wir zuerst unseren Dienst und dann später unsere Nachrichten. Um einen Dienst zu definieren, geben Sie in Ihrer .proto-Datei einen benannten Dienst an. Die Datei proto/routeguide.proto enthält eine service-Struktur namens RouteGuide, die eine oder mehrere Methoden definiert, die vom Dienst der Anwendung bereitgestellt werden.
Definieren Sie RPC-Methoden in Ihrer Dienstdefinition und geben Sie die zugehörigen Anfrage- und Antworttypen an. Definieren wir in diesem Abschnitt des Codelabs Folgendes:
ListFeatures
Ruft die Features ab, die im angegebenen Rectangle verfügbar sind. Die Ergebnisse werden gestreamt und nicht auf einmal zurückgegeben (z.B. in einer Antwortnachricht mit einem wiederholten Feld), da das Rechteck eine große Fläche abdecken und eine große Anzahl von Features enthalten kann.
Ein geeigneter Typ für diesen RPC ist ein serverseitiger Streaming-RPC: Der Client sendet eine Anfrage an den Server und erhält einen Stream, um eine Reihe von Nachrichten zurückzulesen. Der Client liest aus dem zurückgegebenen Stream, bis keine Nachrichten mehr vorhanden sind. Wie Sie in unserem Beispiel sehen, geben Sie eine serverseitige Streaming-Methode an, indem Sie das Schlüsselwort stream vor den Antworttyp setzen.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
RecordRoute
Akzeptiert einen Stream von Points auf einer zurückgelegten Route und gibt nach Abschluss der Route eine RouteSummary zurück.
In diesem Fall scheint ein clientseitiger Streaming-RPC geeignet zu sein: Der Client schreibt eine Reihe von Nachrichten und sendet sie an den Server, wiederum über einen bereitgestellten Stream. Sobald der Client das Schreiben der Nachrichten abgeschlossen hat, wartet er darauf, dass der Server sie alle liest und seine Antwort zurückgibt. Sie geben eine clientseitige Streaming-Methode an, indem Sie das Schlüsselwort stream vor den Anfragetyp setzen.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
RouteChat
Akzeptiert einen Stream von RouteNotes, die während der Zurücklegung einer Route gesendet werden, und empfängt gleichzeitig andere RouteNotes (z.B. von anderen Nutzern).
Dies ist genau der Anwendungsfall für bidirektionales Streaming. Bei einem bidirektionalen Streaming-RPC senden beide Seiten eine Reihe von Nachrichten über einen Lese-/Schreib-Stream. Die beiden Streams arbeiten unabhängig voneinander, sodass Clients und Server in beliebiger Reihenfolge lesen und schreiben können.
Der Server kann beispielsweise warten, bis er alle Clientnachrichten empfangen hat, bevor er seine Antworten schreibt, oder er kann abwechselnd eine Nachricht lesen und dann eine Nachricht schreiben oder eine andere Kombination aus Lese- und Schreibvorgängen verwenden.
Die Reihenfolge der Nachrichten in jedem Stream bleibt erhalten. Sie geben diesen Methodentyp an, indem Sie das Schlüsselwort stream sowohl vor die Anfrage als auch vor die Antwort setzen.
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
4. Client- und Servercode generieren
Wir haben Ihnen den generierten Code aus der .proto-Datei bereits im Verzeichnis generated/ zur Verfügung gestellt, einschließlich aller oben vorgenommenen Ergänzungen. Wir möchten jedoch kurz erläutern, wie die Codegenerierung funktioniert.
In unserer .proto-Datei werden alle Strukturen und Funktionen beschrieben, die ein Client oder Server verwendet. Wir verwenden ein Cargo-Build-Skript (build.rs) zusammen mit der grpc-protobuf-build-Crate, um diesen Code automatisch zu generieren.
In Cargo.toml haben wir grpc-protobuf-build bereits als Build-Abhängigkeit hinzugefügt.
In build.rs konfigurieren wir grpc_protobuf_build::CodeGen, um proto/routeguide.proto in das Verzeichnis generated/ zu kompilieren. Die wichtigsten Zeilen sind hier:
grpc_protobuf_build::CodeGen::new()
.include("proto")
.input("routeguide.proto")
.output_dir("generated")
.compile()
.unwrap();
Dadurch wird die Codegenerierung der grpc_protobuf_build-Crate aufgerufen und routeguide.proto übergeben. Wir haben dies in Code eingeschlossen, damit es nur ausgeführt wird, wenn ein Funktions-Flag übergeben wird. So wird es nur neu generiert, wenn Sie es möchten. Sie müssen dies jetzt nicht ausführen, da wir den Code bereits für Sie generiert haben.
cargo build --bin routeguide-server --features regenerate_proto
Wenn Sie `cargo build` ausführen, kompiliert build.rs die Protocol Buffer-Definitionen in das Verzeichnis generate/, einschließlich:
- Strukturdefinitionen für die Nachrichtentypen
PointundFeature. - Ein Tonic-Dienst-Trait, das wir für den Server implementieren müssen:
route_guide_server::RouteGuide. - Ein gRPC-Rust-Clienttyp, den wir zum Aufrufen des Servers verwenden:
route_guide_client::RouteGuideClient<T>.
Weitere Informationen finden Sie im Leitfaden zu protoc-gen-rust-grpc.
Als Nächstes implementieren wir die Dienstmethoden auf dem Server.
5. Dienst implementieren
Sehen wir uns zuerst an, wie wir einen RouteGuide-Server erstellen. Es gibt zwei Teile, um unseren RouteGuide-Dienst zum Laufen zu bringen:
- Implementieren der Dienstschnittstelle, die aus unserer Dienstdefinition generiert wurde: die eigentliche „Arbeit“ unseres Dienstes.
- Ausführen eines gRPC-Servers, um auf Anfragen von Clients zu warten und sie an die richtige Methodenimplementierung weiterzuleiten.
In src/server/server.rs können wir den generierten Code mit dem include_generated_proto!-Makro von gRPC in den Gültigkeitsbereich aufnehmen und das RouteGuide-Trait und Point importieren.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
pub use grpc_pb::{
route_guide_server::{RouteGuideServer, RouteGuide},
Point, Feature, Rectangle, RouteNote, RouteSummary
};
Wir können mit der Definition einer Struktur beginnen, die unseren Dienst darstellt. Das können wir vorerst in src/server/server.rs tun:
#[derive(Debug)]
pub struct RouteGuideService {
features: Vec<Feature>,
}
Jetzt müssen wir das route_guide_server::RouteGuide-Trait aus unserem generierten Code implementieren.
RouteGuide implementieren
Wir müssen die generierte RouteGuide-Schnittstelle implementieren. So würde die Implementierung aussehen. Das ist bereits in der Vorlage enthalten.
#[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> {
...
}
}
Sehen wir uns die einzelnen RPC-Implementierungen im Detail an.
Serverseitiger Streaming-RPC: ListFeatures
Beginnen wir mit ListFeatures. Dies ist ein serverseitiger Streaming-RPC (der Client sendet eine Nachricht, der Server antwortet mit vielen), daher müssen wir mehrere Features an unseren Client zurücksenden.
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)))
}
Wie Sie sehen, erhalten wir ein Anfrageobjekt (das Rectangle, in dem unser Client Features finden möchte). Dieses Mal müssen wir einen Stream von Werten zurückgeben. Wir erstellen einen Kanal und starten eine neue asynchrone Aufgabe, bei der wir eine Suche durchführen und die Features, die unseren Einschränkungen entsprechen, in den Kanal senden. Die Stream-Hälfte des Kanals wird in ein tonic::Response eingeschlossen an den Aufrufer zurückgegeben.
Clientseitiger Streaming-RPC: RecordRoute
Sehen wir uns nun etwas Komplizierteres an: die clientseitige Streaming-Methode RecordRoute, bei der wir einen Stream von Points vom Client erhalten und eine einzelne RouteSummary mit Informationen zur Reise zurückgeben. Sie erhält einen Stream als Eingabe, mit dem der Server sowohl Nachrichten lesen als auch schreiben kann. Mit der Methode next() kann sie die Clientnachrichten durchlaufen und ihre einzelne Antwort zurückgeben.
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))
}
Im Methodenkörper verwenden wir die Methode next() des Streams, um die Anfragen unseres Clients wiederholt in ein Anfrageobjekt (in diesem Fall ein Point) einzulesen, bis keine Nachrichten mehr vorhanden sind. Wenn dies „None“ ist, ist der Stream noch in Ordnung und kann weiter gelesen werden.
Bidirektionaler Streaming-RPC: RouteChat
Sehen wir uns abschließend unseren bidirektionalen Streaming-RPC RouteChat() an.
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)))
}
Dieses Mal erhalten wir einen Stream, der wie in unserem clientseitigen Streaming-Beispiel zum Lesen und Schreiben von Nachrichten verwendet werden kann. Dieses Mal geben wir jedoch Werte über den Stream unserer Methode zurück, während der Client weiterhin Nachrichten in seinen Nachrichtenstream schreibt. Die Syntax zum Lesen und Schreiben ist hier sehr ähnlich wie bei unserer clientseitigen Streaming-Methode, mit dem Unterschied, dass der Server einen RouteChatStream zurückgibt. Obwohl jede Seite die Nachrichten der anderen Seite immer in der Reihenfolge erhält, in der sie geschrieben wurden, können sowohl der Client als auch der Server in beliebiger Reihenfolge lesen und schreiben. Die Streams arbeiten völlig unabhängig voneinander.
Wir erstellen den Ausgabestream mit try_stream!, was darauf hinweist, dass der Stream Fehler zurückgeben kann.
Server starten
Nachdem wir diese Methode implementiert haben, müssen wir auch einen gRPC-Server starten, damit Clients unseren Dienst tatsächlich nutzen können. Füllen Sie main() aus.
#[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(())
}
So funktioniert main() Schritt für Schritt:
- Geben Sie den Port an, auf dem wir auf Clientanfragen warten möchten.
- Erstellen Sie einen
RouteGuideServicemit geladenen Features. - Erstellen Sie eine Instanz des gRPC-Servers mit
RouteGuideServer::new()und dem von uns erstellten Dienst. - Registrieren Sie unsere Dienstimplementierung beim gRPC-Server.
- Rufen Sie
serve()auf dem Server mit unseren Portdetails auf, um eine blockierende Wartezeit zu erzwingen, bis der Prozess beendet wird.
6. Client erstellen
In diesem Abschnitt sehen wir uns an, wie Sie in src/client/client.rs einen Rust-Client für unseren RouteGuide-Dienst erstellen.
Nehmen Sie zuerst den generierten Code in den Gültigkeitsbereich auf.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
use grpc_pb::route_guide_client::RouteGuideClient;
use grpc_pb::{Point, Rectangle, RouteNote};
Dienstmethoden aufrufen
Sehen wir uns nun an, wie wir unsere Dienstmethoden aufrufen. In gRPC-Rust sind Streaming-RPCs asynchron und nicht blockierend. Sie verwenden die Async/Await-Syntax von Rust und Tokio-Streams.
Serverseitiger Streaming-RPC: PrintFeatures
Bei serverseitigen Streaming-RPCs sendet der Client eine einzelne Anfragenachricht an den Server und erhält einen Stream von Antwortnachrichten zurück. Hier rufen wir in client.rs die serverseitige Streaming-Methode list_features() auf, die der ListFeatures-RPC-Deklaration in unserem Proto entspricht. Der Server sendet wiederum einen Stream von Feature-Nachrichten zurück:
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(())
}
Clientseitiger Streaming-RPC: RecordRoute
Wenn wir clientseitiges Streaming verwenden, öffnet der Client einen Stream zum Server und sendet eine Reihe von Nachrichten. Nach Abschluss des Streams erhält er eine einzelne Antwortnachricht.
Hier initiieren wir den Aufruf mit client.record_route().await, senden mehrere generierte Point-Koordinaten einzeln über den Stream mit stream.send(point).await und schließen dann den Stream mit stream.close_and_recv().await, um die einzelne RouteSummary-Nachricht des Servers zu empfangen.
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(())
}
Bidirektionaler Streaming-RPC: RouteChat
Sehen wir uns abschließend unseren bidirektionalen Streaming-RPC RouteChat() an. Hier senden sowohl der Client als auch der Server eine Reihe von Nachrichten hin und her. Wir starten eine tokio-Aufgabe, um mit tx.send(note).await.is_err() kontinuierlich Nachrichten an den Server zu senden. In der Zwischenzeit wartet rx.recv().await auf Antwortnachrichten vom Server und gibt sie aus, sobald sie eintreffen.
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(())
}
Obwohl jede Seite die Nachrichten der anderen Seite immer in der Reihenfolge erhält, in der sie geschrieben wurden, können sowohl der Client als auch der Server in beliebiger Reihenfolge lesen und schreiben. Die Streams arbeiten völlig unabhängig voneinander.
Client erstellen und übergeben
Um Dienstmethoden aufzurufen, müssen wir zuerst einen Kanal für die Kommunikation mit dem Server erstellen. Dazu erstellen wir zuerst einen Endpunkt, stellen eine Verbindung zu diesem Endpunkt her und übergeben den beim Herstellen der Verbindung erstellten Kanal an RouteGuideClient::new():
// 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);
Nachdem dieser Client erstellt wurde, können wir die oben geschriebenen Methoden aufrufen und den Client an sie übergeben. Wir fügen diesen Code in main() ein, das die asynchrone Tokio-Laufzeit verwendet. Hier ist der vollständige Code:
#[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. Jetzt ausprobieren
Prüfen Sie, ob in Cargo.toml beide binären Ziele definiert sind, um Client und Server auszuführen:
[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"
[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"
Führen Sie dann die folgenden Befehle in unserem Arbeitsverzeichnis aus:
- Führen Sie den Server in einem Terminal aus:
cargo run --bin routeguide-server
- Führen Sie den Client in einem anderen Terminal aus:
cargo run --bin routeguide-client
Die Ausgabe sieht so aus:
*** 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. Nächste Schritte
- Offizielles gRPC-Rust-Repository ansehen.
- Weitere Informationen zur gRPC-Architektur finden Sie unter Kernkonzepte.
- Die gRPC-Rust-Dokumentation auf gRPC.io ansehen.
9. Mitwirkende an diesem Codelab
- Cathy Zhao
- Arvind Bright
- Nathaniel Ford