1. Introduzione
In questo codelab, utilizzerai gRPC-Rust per creare un client e un server che costituiscono la base di un'applicazione di mappatura delle route scritta in Rust.
Al termine del tutorial, avrai un client che si connette a un server remoto utilizzando l'implementazione ufficiale del protocollo gRPC in Rust per recuperare il nome o l'indirizzo postale di una località in coordinate specifiche su una mappa. Un'applicazione completa potrebbe utilizzare questa progettazione client-server per enumerare o riepilogare i punti di interesse lungo una route.
Il servizio è definito in un file Protocol Buffers, che verrà utilizzato per generare il codice boilerplate per il client e il server in modo che possano comunicare tra loro, risparmiando tempo e fatica nell'implementazione di questa funzionalità.
Questo codice generato si occupa non solo delle complessità della comunicazione tra il server e il client, ma anche della serializzazione e deserializzazione dei dati.
Obiettivi didattici
- Come utilizzare Protocol Buffers per definire un'API di servizio.
- Come creare un client basato su gRPC da una definizione di Protocol Buffers utilizzando la generazione automatica del codice.
- Comprendere la comunicazione client-server con gRPC.
Questo codelab è rivolto agli sviluppatori Rust che non hanno familiarità con gRPC o che desiderano aggiornare le proprie conoscenze su gRPC, o a chiunque sia interessato alla creazione di sistemi distribuiti. Non è richiesta alcuna esperienza pregressa con gRPC.
2. Prima di iniziare
Prerequisiti
Assicurati di aver installato quanto segue:
- GCC. Segui le istruzioni riportate qui.
- Git: istruzioni di installazione qui.
- Rust, versione 1.88.0. Segui le istruzioni di installazione qui.
Ottieni il codice
Per non dover ricominciare da zero, questo codelab fornisce uno scheletro del codice sorgente dell'applicazione da completare. I passaggi seguenti ti mostreranno come completare l'applicazione, incluso l'utilizzo dei plug-in del compilatore di buffer di protocollo per generare il codice gRPC boilerplate.
Innanzitutto, crea la directory di lavoro del codelab e accedi tramite cd:
mkdir grpc-rust-getting-started && cd grpc-rust-getting-started
Scarica ed estrai il 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
In alternativa, puoi scaricare il file .zip contenente solo la directory del codelab ed estrarlo manualmente.
Il codice sorgente completato è disponibile su GitHub se non vuoi digitare un'implementazione.
3. Definisci il servizio
Il primo passo consiste nel definire il servizio gRPC dell'applicazione, il relativo metodo RPC e i tipi di messaggi di richiesta e risposta utilizzando Protocol Buffers. Il servizio fornirà:
- Un metodo RPC chiamato
GetFeatureche il server implementa e il client chiama. - I tipi di messaggi
PointeFeatureche sono strutture di dati scambiate tra il client e il server quando si utilizza il metodoGetFeature. Il client fornisce le coordinate della mappa comePointnella richiestaGetFeatureal server e il server risponde con unaFeaturecorrispondente che descrive ciò che si trova in quelle coordinate.
Questo metodo RPC e i relativi tipi di messaggi verranno definiti nel file proto/routeguide.proto del codice sorgente fornito.
Protocol Buffers sono comunemente noti come protobuf. Per ulteriori informazioni sulla terminologia gRPC, consulta Concetti fondamentali, architettura e ciclo di vita di gRPC.
Metodo di servizio
Definiamo prima i metodi di servizio e poi i tipi di messaggi Point e Feature. Il file proto/routeguide.proto ha una struttura service denominata RouteGuide che definisce uno o più metodi forniti dal servizio dell'applicazione.
Aggiungi il metodo rpc GetFeature all'interno della definizione RouteGuide. Come spiegato in precedenza, questo metodo cercherà il nome o l'indirizzo di una località da un determinato insieme di coordinate, quindi fai in modo che GetFeature restituisca un Feature per un determinato Point:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
Si tratta di un metodo RPC unitario: un RPC semplice in cui il client invia una richiesta al server e attende una risposta, proprio come una chiamata di funzione locale.
Tipi di messaggi
Nel file proto/routeguide.proto del codice sorgente, definisci prima il tipo di messaggio Point. Un Point rappresenta una coppia di coordinate di latitudine e longitudine su una mappa. Per questo codelab, utilizza numeri interi per le coordinate:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
I numeri 1 e 2 sono numeri ID univoci per ciascuno dei campi nella struttura message.
Definisci quindi il tipo di messaggio Feature. Una Feature utilizza un campo string per il nome o l'indirizzo postale di un elemento in una località specificata da un Point:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
4. Genera il codice client e server
Ti abbiamo già fornito il codice generato dal file .proto nella directory generated/, incluse tutte le aggiunte che hai apportato sopra. Tuttavia, vorremmo dedicare un momento a spiegare come funziona la generazione del codice.
Il nostro file .proto descrive tutte le struct e le funzioni utilizzate da un client o un server. Utilizziamo uno script di build di Cargo (build.rs) insieme alla crate grpc-protobuf-build per generare automaticamente questo codice.
In Cargo.toml aggiungiamo grpc-protobuf-build in [build-dependencies].
In build.rs, configuriamo grpc_protobuf_build::CodeGen per compilare proto/routeguide.proto nella directory generated/. Le righe chiave sono le seguenti:
grpc_protobuf_build::CodeGen::new()
.include("proto")
.input("routeguide.proto")
.output_dir("generated")
.compile()
.unwrap();
Viene chiamata la generazione del codice della crate grpc_protobuf_build, a cui viene passato routeguide.proto. Abbiamo inserito questo codice in modo che venga eseguito solo quando viene passato un flag di funzionalità, in modo che venga rigenerato solo quando vuoi. Non devi eseguirlo ora, perché abbiamo già generato il codice per te.
cargo build --bin routeguide-server --features regenerate_proto
Quando esegui cargo build, build.rs compila le definizioni di buffer di protocollo nella directory generate/, tra cui:
- Definizioni di struct per i tipi di messaggi
PointeFeature. - Un tratto di servizio Tonic che dovremo implementare per il server:
route_guide_server::RouteGuide. - Un tipo di client gRPC-Rust che utilizzeremo per chiamare il server:
route_guide_client::RouteGuideClient<T>.
Per ulteriori informazioni, consulta la guida protoc-gen-rust-grpc.
A questo punto, implementeremo i metodi di servizio sul server.
5. Implementa il servizio
In src/server/server.rs, possiamo portare il codice generato nell'ambito tramite la macro include_generated_proto! di gRPC e importare il tratto RouteGuide e Point.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
use grpc_pb::{
route_guide_server::{RouteGuideServer, RouteGuide},
Point, Feature,
};
Possiamo iniziare definendo una struct per rappresentare il nostro servizio, per il momento possiamo farlo in src/server/server.rs:
#[derive(Debug)]
pub struct RouteGuideService {
features: Vec<Feature>,
}
Ora dobbiamo implementare il tratto route_guide_server::RouteGuide dal codice generato.
RPC unitario semplice
Il RouteGuideService implementa tutti i nostri metodi di servizio. La funzione get_feature sul lato server è dove viene eseguito il lavoro principale: accetta un messaggio Point dal client e restituisce in un messaggio Feature le informazioni sulla località corrispondenti da un elenco di luoghi noti. Ecco l'implementazione della funzione 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()))
}
}
Una volta implementato questo metodo, dobbiamo anche avviare un server gRPC in modo che i client possano effettivamente utilizzare il nostro servizio. Sostituisci main() con questo.
#[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(())
}
Ecco cosa succede in main(), passo dopo passo:
- Specifica la porta che vogliamo utilizzare per ascoltare le richieste dei client
- Crea un
RouteGuideServicecon le funzionalità caricate chiamando la funzione helperload() - Crea un'istanza del server gRPC utilizzando
RouteGuideServer::new()utilizzando il servizio che abbiamo creato. - Registra l'implementazione del servizio con il server gRPC.
- Chiama
serve()sul server con i dettagli della porta per eseguire un'attesa bloccante fino all'interruzione del processo.
6. Crea il client
In questa sezione esamineremo la creazione di un client Rust per il nostro servizio RouteGuide in src/client/client.rs.
Come abbiamo fatto in src/server/server.rs, possiamo portare il codice generato nell'ambito tramite la macro include_generated_proto! di gRPC e importare il tipo RouteGuideClient.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
use grpc_pb::{
route_guide_client::RouteGuideClient,
Point,
};
Chiama i metodi di servizio
In gRPC-Rust, gli RPC sono asincroni e non bloccanti e utilizzano la sintassi async/await di Rust per attendere le risposte dal server.
Per chiamare i metodi di servizio, creiamo prima un Channel utilizzando Channel::builder(), specificando l'indirizzo del server (dns:///[::1]:10000) e le credenziali di connessione (LocalChannelCredentials). Quindi passiamo il canale a RouteGuideClient::new() per creare un'istanza del client:
#[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 questa funzione, RouteGuideClient::new() associa il canale generico allo stub client generato che implementa i metodi definiti nella definizione del servizio .proto.
RPC semplice
La chiamata all'RPC semplice GetFeature è semplice come chiamare un metodo locale. Aggiungi questo in 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");
In gRPC-Rust, passiamo il messaggio protobuf Point direttamente a client.get_feature(point). L'attesa del futuro restituito produce direttamente la risposta Feature, senza la necessità di ulteriori chiamate di metodo.
A questo punto, stampa i campi della risposta:
println!(
"Response = Name = \"{}\", Latitude = {}, Longitude = {}",
response.name(),
response.location().latitude(),
response.location().longitude()
);
In totale, la funzione main() del client dovrebbe essere simile alla seguente:
#[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. Prova
Per eseguire il client e il server, verifica innanzitutto che entrambi i target binari siano definiti in Cargo.toml:
[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"
[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"
Quindi, esegui i seguenti comandi dalla directory di lavoro:
- Esegui il server in un terminale:
cargo run --bin routeguide-server
- Esegui il client da un altro terminale:
cargo run --bin routeguide-client
L'output dovrebbe essere simile al seguente, con i timestamp omessi per chiarezza:
*** SIMPLE RPC ***
Response = Name = "Berkshire Valley Management Area Trail, Jefferson, NJ, USA", Latitude = 409146138, Longitude = -746188906
8. Passaggi successivi
- Continua con il codelab Introduzione a gRPC-Rust (streaming).
- Esplora il repository ufficiale di gRPC-Rust.
- Scopri di più sull'architettura gRPC in Concetti fondamentali.
- Visualizza la documentazione di gRPC-Rust su gRPC.io.
9. Hanno collaborato alla stesura di questo codelab
- Cathy Zhao
- Lucio Franco
- Arvind Bright
- Nathaniel Ford