Erste Schritte mit gRPC-Rust

1. Einführung

In diesem Codelab erstellen Sie mit gRPC-Rust einen Client und einen Server, die die Grundlage einer in Rust geschriebenen Routenplanungsanwendung bilden.

Am Ende des Tutorials haben Sie einen Client, der über die offizielle Rust-Implementierung des gRPC-Protokolls eine Verbindung zu einem Remote-Server herstellt, um den Namen oder die Postadresse eines Ortes mit bestimmten Koordinaten auf einer Karte abzurufen. Eine voll funktionsfähige Anwendung könnte dieses Client-Server-Design verwenden, um Points of Interest entlang einer Route aufzulisten oder zusammenzufassen.

Der Dienst wird in einer Protocol Buffers-Datei definiert, mit der Boilerplate-Code für den Client und den Server generiert wird, 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 aus einer Protocol Buffers-Definition mithilfe der automatischen Codegenerierung.
  • Grundlagen der Client-Server-Kommunikation 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 dazu finden Sie hier.

Code abrufen

Damit Sie nicht ganz von vorn anfangen müssen, bietet dieses Codelab ein Gerüst des Quellcodes der Anwendung, das Sie vervollständigen können. In den folgenden Schritten erfahren Sie, wie Sie die Anwendung fertigstellen, einschließlich der Verwendung der Protocol Buffers-Compiler-Plug-ins zum Generieren des Boilerplate-gRPC-Codes.

Erstellen Sie zuerst das Arbeitsverzeichnis für das Codelab und wechseln Sie zu diesem Verzeichnis:

mkdir grpc-rust-getting-started && cd 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-getting-started/start_here

Alternativ können Sie die ZIP-Datei herunterladen, die nur das Codelab-Verzeichnis enthält, und sie manuell entpacken.

Der vollständige Quellcode ist auf GitHub verfügbar, wenn Sie die Implementierung überspringen möchten.

3. Dienst definieren

Im ersten Schritt definieren Sie den gRPC-Dienst der Anwendung, die zugehörige RPC-Methode sowie die Nachrichtenarten für Anfragen und Antworten mithilfe von Protocol Buffers. Ihr Dienst bietet Folgendes:

  • Eine RPC-Methode namens GetFeature, die vom Server implementiert und vom Client aufgerufen wird.
  • Die Nachrichtenarten Point und Feature, die Datenstrukturen sind, die zwischen Client und Server ausgetauscht werden, wenn die Methode GetFeature verwendet wird. Der Client gibt in seiner GetFeature-Anfrage an den Server Kartenkoordinaten als ein Point an. Der Server antwortet mit einem entsprechenden Feature, das beschreibt, was sich an diesen Koordinaten befindet.

Diese RPC-Methode und die zugehörigen Nachrichtenarten werden 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.

Dienstmethode

Definieren wir zuerst unsere Dienstmethoden und dann unsere Nachrichtenarten Point und Feature. 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.

Fügen Sie die rpc-Methode GetFeature in die Definition RouteGuide ein. Wie bereits erwähnt, sucht diese Methode den Namen oder die Adresse eines Ortes anhand einer bestimmten Menge von Koordinaten. Daher sollte GetFeature ein Feature für einen bestimmten Point zurückgeben:

service RouteGuide {
  // Definition of the service goes here

  // Obtains the feature at a given position.
  rpc GetFeature(Point) returns (Feature) {}
}

Dies ist eine unäre RPC-Methode: ein einfacher RPC, bei dem der Client eine Anfrage an den Server sendet und auf eine Antwort wartet, genau wie bei einem lokalen Funktionsaufruf.

Mitteilungstypen

Definieren Sie zuerst die Nachrichtenart Point in der Datei proto/routeguide.proto des Quellcodes. Ein Point stellt ein Koordinatenpaar für Längen- und Breitengrad 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 die Nachrichtenart 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;
}

4. Client- und Servercode generieren

Wir haben Ihnen den generierten Code aus der Datei .proto bereits im Verzeichnis generated/ zur Verfügung gestellt, einschließlich aller oben vorgenommenen Ergänzungen. Wir möchten jedoch kurz erklären, wie die Codegenerierung funktioniert.

In unserer Datei .proto 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 fügen wir grpc-protobuf-build unter [build-dependencies] hinzu.

In build.rs konfigurieren wir grpc_protobuf_build::CodeGen so, dass proto/routeguide.proto in das Verzeichnis generated/ kompiliert wird. 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, der nur ausgeführt wird, wenn ein Funktions-Flag übergeben wird, damit es nur dann neu generiert wird, 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 Buffers-Definitionen in das Verzeichnis generate/, einschließlich:

  • Strukturdefinitionen für die Nachrichtenarten Point und Feature.
  • 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

In src/server/server.rs können wir den generierten Code über das Makro include_generated_proto! von gRPC in den Gültigkeitsbereich aufnehmen und das Trait RouteGuide und Point importieren.

mod grpc_pb {
    grpc::include_generated_proto!("generated", "routeguide");
}

use grpc_pb::{
    route_guide_server::{RouteGuideServer, RouteGuide},
    Point, Feature,
};

Wir können zuerst eine Struktur definieren, 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 Trait route_guide_server::RouteGuide aus unserem generierten Code implementieren.

Einfacher unärer RPC

Der RouteGuideService implementiert alle unsere Dienstmethoden. Die Funktion get_feature auf der Serverseite ist der Ort, an dem die Hauptarbeit erledigt wird: Sie nimmt eine Point-Nachricht vom Client entgegen und gibt in einer Feature-Nachricht die entsprechenden Standortinformationen aus einer Liste bekannter Orte zurück. Hier ist die Implementierung der Funktion in 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()))
    }
}

Nachdem wir diese Methode implementiert haben, müssen wir auch einen gRPC-Server starten, damit Clients unseren Dienst tatsächlich nutzen können. Ersetzen Sie main() durch Folgendes:

#[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(())
}

Hier sehen Sie Schritt für Schritt, was in main() passiert:

  1. Geben Sie den Port an, auf dem wir auf Clientanfragen warten möchten.
  2. Erstellen Sie einen RouteGuideService mit geladenen Features, indem Sie die Hilfsfunktion load() aufrufen.
  3. Erstellen Sie mit RouteGuideServer::new() eine Instanz des gRPC-Servers mit dem von uns erstellten Dienst.
  4. Registrieren Sie unsere Dienstimplementierung beim gRPC-Server.
  5. 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.

Wie in src/server/server.rs können wir den generierten Code über das Makro include_generated_proto! von gRPC in den Gültigkeitsbereich aufnehmen und den Typ RouteGuideClient importieren.

mod grpc_pb {
    grpc::include_generated_proto!("generated", "routeguide");
}

use grpc_pb::{
    route_guide_client::RouteGuideClient,
    Point,
};

Dienstmethoden aufrufen

In gRPC-Rust sind RPCs asynchron und nicht blockierend. Sie verwenden die async/await-Syntax von Rust, um auf Antworten vom Server zu warten.

Um Dienstmethoden aufzurufen, erstellen wir zuerst einen Channel mit Channel::builder(), wobei wir die Serveradresse (dns:///[::1]:10000) und die Anmeldedaten für die Verbindung (LocalChannelCredentials) angeben. Anschließend übergeben wir den Channel an RouteGuideClient::new(), um unseren Client zu instanziieren:

#[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);



}

In dieser Funktion bindet RouteGuideClient::new() den generischen Channel an den generierten Client-Stub, der die in unserer .proto-Dienstdefinition definierten Methoden implementiert.

Einfacher RPC

Der Aufruf des einfachen RPC GetFeature ist so einfach wie der Aufruf einer lokalen Methode. Fügen Sie Folgendes in main() ein:

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");

In gRPC-Rust übergeben wir die Protobuf-Nachricht Point direkt an client.get_feature(point). Wenn wir auf die zurückgegebene Future warten, erhalten wir die Feature-Antwort direkt, ohne dass weitere Methodenaufrufe erforderlich sind.

Geben Sie als Nächstes die Felder aus der Antwort aus:

println!(
    "Response = Name = \"{}\", Latitude = {}, Longitude = {}",
    response.name(),
    response.location().latitude(),
    response.location().longitude()
);

Insgesamt sollte die main()-Funktion des Clients so aussehen:

#[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. Jetzt ausprobieren

Um Ihren Client und Server auszuführen, prüfen Sie zuerst, ob beide binären Ziele in Cargo.toml definiert sind:

[[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:

  1. Führen Sie den Server in einem Terminal aus:
cargo run --bin routeguide-server
  1. Führen Sie den Client in einem anderen Terminal aus:
cargo run --bin routeguide-client

Die Ausgabe sieht so aus, wobei die Zeitstempel zur besseren Übersichtlichkeit weggelassen wurden:

*** SIMPLE RPC ***
Response = Name = "Berkshire Valley Management Area Trail, Jefferson, NJ, USA", Latitude = 409146138, Longitude = -746188906

8. Nächste Schritte

9. Mitwirkende an diesem Codelab

  • Cathy Zhao
  • Lucio Franco
  • Arvind Bright
  • Nathaniel Ford