1. Introduzione
In questo codelab, utilizzerai gRPC-Java per creare un client e un server che costituiscono la base di un'applicazione di mappatura di percorsi scritta in Java.
Al termine del tutorial, avrai un client che si connette a un server remoto utilizzando gRPC per ottenere il nome o l'indirizzo postale di ciò che si trova in coordinate specifiche su una mappa. Un'applicazione completa potrebbe utilizzare questa progettazione client-server per enumerare o riepilogare i punti di interesse lungo un percorso.
L'API del server è definita in un file Protocol Buffers, che verrà utilizzato per generare 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 della deserializzazione dei dati.
Obiettivi didattici
- Come utilizzare i buffer di protocollo per definire un'API di servizio.
- Come creare un client e un server basati su gRPC da una definizione di Protocol Buffers utilizzando la generazione automatica del codice.
- Comprensione della comunicazione client-server con gRPC.
Questo codelab è rivolto agli sviluppatori Java che non hanno mai utilizzato gRPC o che vogliono ripassare le basi di gRPC, nonché a chiunque sia interessato a creare sistemi distribuiti. Non è richiesta alcuna esperienza precedente con gRPC.
2. Prima di iniziare
Prerequisiti
- JDK versione 8 o successive
Ottieni il codice
Per non dover iniziare da zero, questo codelab fornisce una struttura del codice sorgente dell'applicazione da completare. I passaggi seguenti mostrano come completare l'applicazione, incluso l'utilizzo dei plug-in del compilatore di protocol buffer per generare il codice gRPC boilerplate.
Innanzitutto, crea la directory di lavoro del codelab e accedi tramite cd:
mkdir grpc-java-getting-started && cd grpc-java-getting-started
Scarica ed estrai il codelab:
curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
| tar xvz --strip-components=4 \
grpc-codelabs-1/codelabs/grpc-java-getting-started/start_here
In alternativa, puoi scaricare il file .zip contenente solo la directory del codelab e decomprimerlo manualmente.
Il codice sorgente completato è disponibile su GitHub se vuoi evitare di digitare un'implementazione.
3. Definisci il servizio
Il primo passaggio 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 tuo servizio fornirà:
- Un metodo RPC chiamato
GetFeature
che il server implementa e il client chiama. - I tipi di messaggio
Point
eFeature
, che sono strutture di dati scambiate tra il client e il server quando si utilizza il metodoGetFeature
. Il client fornisce le coordinate della mappa comePoint
nella richiestaGetFeature
al server e il server risponde con unFeature
corrispondente che descrive ciò che si trova a quelle coordinate.
Questo metodo RPC e i relativi tipi di messaggio verranno definiti nel file src/main/proto/routeguide/route_guide.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.
Poiché in questo esempio generiamo codice Java, abbiamo specificato un'opzione per il file java_package
e un nome per la classe Java nel nostro .proto
:
option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";
Tipi di messaggi
Nel file routeguide/route_guide.proto
del codice sorgente, definisci innanzitutto 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
.
Successivamente, definisci il tipo di messaggio Feature
. Un 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;
}
Metodo di servizio
Il file route_guide.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 di RouteGuide
. Come spiegato in precedenza, questo metodo cerca il nome o l'indirizzo di una località da un determinato insieme di coordinate, quindi GetFeature
restituisce 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 unario: una RPC semplice in cui il client invia una richiesta al server e attende una risposta, proprio come una chiamata di funzione locale.
4. Genera codice client e server
Successivamente, dobbiamo generare le interfacce client e server gRPC dalla definizione del servizio .proto
. A questo scopo, utilizziamo il compilatore di protocol buffer protoc
con un plug-in Java gRPC speciale. Per generare servizi gRPC, devi utilizzare il compilatore proto3 (che supporta sia la sintassi proto2 sia quella proto3).
Quando utilizzi Gradle o Maven, il plug-in di compilazione protoc
può generare il codice necessario come parte della compilazione. Per informazioni su come generare codice dai tuoi file .proto
, consulta il file README di grpc-java.
Nel codice sorgente del codelab abbiamo fornito un ambiente e una configurazione Gradle per creare questo progetto.
All'interno della directory grpc-java-getting-started
, esegui questo comando:
$ chmod +x gradlew $ ./gradlew generateProto
Le seguenti classi vengono generate dalla nostra definizione di servizio:
Feature.java
,Point.java
e altri che contengono tutto il codice del buffer del protocollo per popolare, serializzare e recuperare i nostri tipi di messaggi di richiesta e risposta.RouteGuideGrpc.java
che contiene (insieme ad altro codice utile) una classe base per l'implementazione dei serverRouteGuide
,RouteGuideGrpc.RouteGuideImplBase
, con tutti i metodi definiti nel servizioRouteGuide
e classi stub da utilizzare per i client.
5. Implementare il server
Per prima cosa, vediamo come creare un server RouteGuide
. Il funzionamento del nostro servizio RouteGuide
si basa su due elementi:
- Implementazione dell'interfaccia di servizio generata dalla nostra definizione di servizio, che svolge il "lavoro" effettivo del nostro servizio.
- Esecuzione di un server gRPC per ascoltare le richieste dei client e inviarle all'implementazione del servizio corretta.
Implementa RouteGuide
Come puoi vedere, il nostro server ha una classe RouteGuideService
che estende la classe astratta RouteGuideGrpc.RouteGuideImplBase
generata:
private static class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
...
}
Abbiamo fornito i seguenti due file per inizializzare il server con le funzionalità:
./src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java
./src/main/resources/io/grpc/examples/routeguide/route_guide_db.json
Esaminiamo in dettaglio una semplice implementazione RPC.
RPC unario
RouteGuideService
implementa tutti i nostri metodi di servizio. In questo caso, è solo GetFeature()
, prende un messaggio Point
dal client e restituisce in un messaggio Feature
le informazioni sulla posizione corrispondenti da un elenco di luoghi noti.
@Override
public void getFeature(Point request, StreamObserver<Feature> responseObserver) {
responseObserver.onNext(checkFeature(request));
responseObserver.onCompleted();
}
Il metodo getFeature()
accetta due parametri:
Point
: la richiesta.StreamObserver<Feature>
: un osservatore di risposta, ovvero un'interfaccia speciale che il server chiama con la sua risposta.
Per restituire la nostra risposta al cliente e completare la chiamata:
- Costruiamo e popoliamo un oggetto di risposta
Feature
da restituire al client, come specificato nella definizione del servizio. In questo esempio, lo facciamo in un metodo privatocheckFeature()
separato. - Utilizziamo il metodo
onNext()
dell'observer della risposta per restituireFeature
. - Utilizziamo il metodo
onCompleted()
dell'observer di risposta per specificare che abbiamo terminato di gestire la RPC.
Avviare il server
Una volta implementati tutti i nostri metodi di servizio, dobbiamo avviare un server gRPC in modo che i client possano effettivamente utilizzare il nostro servizio. Nel nostro boilerplate includiamo la creazione dell'oggetto ServerBuilder:
ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile)
Costruiamo il servizio nel costruttore:
- Specifica la porta che vogliamo utilizzare per ascoltare le richieste dei client utilizzando il metodo
forPort()
del builder (utilizzerà l'indirizzo jolly). - Crea un'istanza della nostra classe di implementazione del servizio
RouteGuideService
e passala al metodoaddService()
del builder. - Chiama
build()
sul builder per creare un server RPC per il nostro servizio.
Il seguente snippet mostra come creare un oggetto ServerBuilder
.
/** Create a RouteGuide server listening on {@code port} using {@code featureFile} database. */
public RouteGuideServer(int port, URL featureFile) throws IOException {
this(Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()),
port, RouteGuideUtil.parseFeatures(featureFile));
}
Il seguente snippet mostra come creare un oggetto server per il nostro servizio RouteGuide
.
/** Create a RouteGuide server using serverBuilder as a base and features as data. */
public RouteGuideServer(ServerBuilder<?> serverBuilder, int port, Collection<Feature> features) {
this.port = port;
server = serverBuilder.addService(new RouteGuideService(features))
.build();
}
Implementa un metodo di avvio che chiama start
sul server che abbiamo creato sopra.
public void start() throws IOException {
server.start();
logger.info("Server started, listening on " + port);
}
Implementa un metodo per attendere il completamento del server in modo che non venga chiuso immediatamente.
/** Await termination on the main thread since the grpc library uses daemon threads. */
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
Come puoi vedere, creiamo e avviamo il nostro server utilizzando un ServerBuilder
.
Nel metodo principale:
- Crea un'istanza
RouteGuideServer
. - Chiama
start()
per attivare un server RPC per il nostro servizio. - Attendi l'interruzione del servizio chiamando il numero
blockUntilShutdown()
.
public static void main(String[] args) throws Exception {
RouteGuideServer server = new RouteGuideServer(8980);
server.start();
server.blockUntilShutdown();
}
6. Crea il client
In questa sezione vedremo come creare un client per il nostro servizio RouteGuide
.
Istanziare uno stub
Per chiamare i metodi di servizio, dobbiamo prima creare uno stub. Esistono due tipi di stub, ma per questo codelab dobbiamo utilizzare solo quello di blocco. I due tipi sono:
- uno stub bloccante/sincrono che effettua una chiamata RPC e attende la risposta del server, quindi restituisce una risposta o genera un'eccezione.
- uno stub non bloccante/asincrono che effettua chiamate non bloccanti al server, dove la risposta viene restituita in modo asincrono. Puoi effettuare determinati tipi di chiamate in streaming solo utilizzando lo stub asincrono.
Per prima cosa, dobbiamo creare un canale gRPC e poi utilizzarlo per creare lo stub.
Avremmo potuto utilizzare un ManagedChannelBuilder
direttamente per creare il canale.
ManagedChannelBuilder.forAddress(host, port).usePlaintext().build
Utilizziamo invece un metodo di utilità che accetta una stringa con hostname:port
.
Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build();
Ora possiamo utilizzare il canale per creare lo stub di blocco. Per questo codelab, abbiamo solo RPC bloccanti, quindi utilizziamo il metodo newBlockingStub
fornito nella classe RouteGuideGrpc
che abbiamo generato dal nostro .proto
.
blockingStub = RouteGuideGrpc.newBlockingStub(channel);
Metodi di servizio di chiamata
Ora vediamo come chiamiamo i metodi del servizio.
RPC semplice
Chiamare l'RPC semplice GetFeature
è quasi semplice come chiamare un metodo locale.
Creiamo e compiliamo un oggetto buffer di protocollo di richiesta (nel nostro caso Point
), lo passiamo al metodo getFeature()
sullo stub di blocco e riceviamo un Feature
.
Se si verifica un errore, viene codificato come Status
, che possiamo ottenere da StatusRuntimeException
.
Point request = Point.newBuilder().setLatitude(lat).setLongitude(lon).build();
Feature feature;
try {
feature = blockingStub.getFeature(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
Il boilerplate registra un messaggio contenente i contenuti in base alla presenza o meno di una funzionalità nel punto specificato.
7. Prova
- All'interno della directory
start_here
, esegui questo comando:
$ ./gradlew installDist
In questo modo, il codice verrà compilato, inserito in un file JAR e verranno creati gli script che eseguono l'esempio. Verranno creati nella directory build/install/start_here/bin/
. I copioni sono: route-guide-server
e route-guide-client
.
Il server deve essere in esecuzione prima di avviare il client.
- Esegui il server:
$ ./build/install/start_here/bin/route-guide-server
- Esegui il client:
$ ./build/install/start_here/bin/route-guide-client
Vedrai un output simile a questo, con i timestamp omessi per chiarezza:
INFO: *** GetFeature: lat=409,146,138 lon=-746,188,906 INFO: Found feature called "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" at 40.915, -74.619 INFO: *** GetFeature: lat=0 lon=0 INFO: Found no feature at 0, 0
8. Passaggi successivi
- Scopri come funziona gRPC in Introduzione a gRPC e Concetti di base.
- Completa il tutorial sulle nozioni di base.
- Esplora il riferimento API.