1. Introdução
Neste codelab, você vai usar o gRPC-Java para criar um cliente e um servidor que formam a base de um aplicativo de mapeamento de rotas escrito em Java.
Ao final do tutorial, você terá um cliente que se conecta a um servidor remoto usando gRPC para receber informações sobre recursos em uma rota de cliente, criar um resumo de uma rota de cliente e trocar informações de rota, como atualizações de trânsito, com o servidor e outros clientes.
O serviço é definido em um arquivo Protocol Buffers, que será usado para gerar código boilerplate para o cliente e o servidor, permitindo que eles se comuniquem entre si e economizando tempo e esforço na implementação dessa funcionalidade.
Esse código gerado cuida não apenas das complexidades da comunicação entre o servidor e o cliente, mas também da serialização e desserialização de dados.
O que você vai aprender
- Como usar buffers de protocolo para definir uma API de serviço.
- Como criar um cliente e um servidor baseados em gRPC com uma definição de buffers de protocolo usando a geração automática de código.
- Entendimento da comunicação de streaming cliente-servidor com gRPC.
Este codelab é destinado a desenvolvedores Java que não conhecem o gRPC ou querem relembrar o assunto, além de qualquer pessoa interessada em criar sistemas distribuídos. Não é necessário ter experiência com gRPC.
2. Antes de começar
Pré-requisitos
- Versão 24 do JDK.
Acessar o código
Para que você não precise começar do zero, este codelab oferece um scaffold do código-fonte do aplicativo para você concluir. As etapas a seguir mostram como concluir o aplicativo, incluindo o uso dos plug-ins do compilador de buffer de protocolo para gerar o código gRPC boilerplate.
Primeiro, crie o diretório de trabalho do codelab e use cd para acessar ele:
mkdir streaming-grpc-java-getting-started && cd streaming-grpc-java-getting-started
Faça o download e extraia o 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-streaming/start_here
Como alternativa, baixe o arquivo .zip que contém apenas o diretório do codelab e descompacte-o manualmente.
O código-fonte completo está disponível no GitHub se você quiser pular a digitação de uma implementação.
3. Definir mensagens e serviços
A primeira etapa é definir o serviço gRPC do aplicativo, o método RPC e os tipos de mensagens de solicitação e resposta usando buffers de protocolo. Seu serviço vai oferecer:
- Métodos RPC chamados
ListFeatures
,RecordRoute
eRouteChat
que o servidor implementa e o cliente chama. - Os tipos de mensagem
Point
,Feature
,Rectangle
,RouteNote
eRouteSummary
, que são estruturas de dados trocadas entre o cliente e o servidor ao chamar os métodos acima.
Os buffers de protocolo são conhecidos como protobufs. Para mais informações sobre a terminologia do gRPC, consulte Conceitos principais, arquitetura e ciclo de vida do gRPC.
Esse método RPC e os tipos de mensagem dele serão definidos no arquivo proto/routeguide/route_guide.proto
do código-fonte fornecido.
Vamos criar um arquivo route_guide.proto
.
Como estamos gerando código Java neste exemplo, especificamos uma opção de arquivo java_package
no nosso .proto
:
option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";
Definir tipos de mensagem
No arquivo proto/routeguide/route_guide.proto
do código-fonte, primeiro defina o tipo de mensagem Point
. Um Point
representa um par de coordenadas de latitude e longitude em um mapa. Neste codelab, use números inteiros para as coordenadas:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
Os números 1
e 2
são IDs exclusivos para cada um dos campos na estrutura message
.
Em seguida, defina o tipo de mensagem Feature
. Um Feature
usa um campo string
para o nome ou endereço postal de algo em um local especificado por um Point
:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
Para que vários pontos em uma área possam ser transmitidos para um cliente, você vai precisar de uma mensagem Rectangle
que represente um retângulo de latitude e longitude, representado como dois pontos diagonalmente opostos lo
e hi
:
message Rectangle {
// One corner of the rectangle.
Point lo = 1;
// The other corner of the rectangle.
Point hi = 2;
}
Além disso, uma mensagem RouteNote
que representa uma mensagem enviada em um determinado ponto:
message RouteNote {
// The location from which the message is sent.
Point location = 1;
// The message to be sent.
string message = 2;
}
Por fim, você vai precisar de uma mensagem RouteSummary
. Essa mensagem é recebida em resposta a uma RPC RecordRoute
, que é explicada na próxima seção. Ele contém o número de pontos individuais recebidos, o número de recursos detectados e a distância total percorrida como a soma cumulativa da distância entre cada ponto.
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;
}
Definir métodos de serviço
Para definir um serviço, especifique um serviço nomeado no arquivo .proto
. O arquivo route_guide.proto
tem uma estrutura service
chamada RouteGuide
que define um ou mais métodos fornecidos pelo serviço do aplicativo.
Ao definir métodos RPC
na definição do serviço, você especifica os tipos de solicitação e resposta. Nesta seção do codelab, vamos definir:
ListFeatures
Recebe os objetos Feature
disponíveis no Rectangle
especificado. Os resultados são transmitidos em vez de retornados de uma só vez, já que o retângulo pode abranger uma grande área e conter um grande número de recursos.
Para esse aplicativo, você vai usar uma RPC de streaming do lado do servidor: o cliente envia uma solicitação ao servidor e recebe um stream para ler uma sequência de mensagens. O cliente lê o stream retornado até que não haja mais mensagens. Como você pode ver no exemplo, especifique um método de streaming do lado do servidor colocando a palavra-chave "stream" antes do tipo de resposta.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
RecordRoute
Aceita um fluxo de pontos em uma rota percorrida, retornando um RouteSummary
quando o percurso é concluído.
Um RPC de streaming do lado do cliente é adequado nesse caso: o cliente grava uma sequência de mensagens e as envia ao servidor, novamente usando um stream fornecido. Depois que o cliente terminar de gravar as mensagens, ele vai aguardar o servidor ler todas elas e retornar a resposta. Para especificar um método de streaming do lado do cliente, coloque a palavra-chave "stream" antes do tipo de solicitação.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
RouteChat
Aceita um fluxo de RouteNotes
enviado enquanto uma rota está sendo percorrida, recebendo outros RouteNotes
(por exemplo, de outros usuários).
Esse é exatamente o tipo de caso de uso para streaming bidirecional. Um RPC de streaming bidirecional em que os dois lados enviam uma sequência de mensagens usando um stream de leitura e gravação. Os dois streams operam de forma independente. Assim, clientes e servidores podem ler e gravar na ordem que quiserem. Por exemplo, o servidor pode esperar para receber todas as mensagens do cliente antes de gravar as respostas ou pode ler uma mensagem e gravar uma mensagem alternadamente ou alguma outra combinação de leituras e gravações. A ordem das mensagens em cada stream é preservada. Para especificar esse tipo de método, coloque a palavra-chave "stream" antes da solicitação e da resposta.
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
4. Gerar código de cliente e servidor
Em seguida, precisamos gerar as interfaces do cliente e do servidor gRPC com base na definição do serviço .proto
. Para isso, usamos o compilador de buffer de protocolo protoc
com um plug-in especial do gRPC Java. É necessário usar o compilador proto3 (que oferece suporte à sintaxe proto2 e proto3) para gerar serviços gRPC.
Ao usar Gradle ou Maven, o plug-in de build protoc pode gerar o código necessário como parte do build. Consulte o README do grpc-java para saber como gerar código dos seus próprios arquivos .proto
.
Fornecemos a configuração do Gradle.
No diretório streaming-grpc-java-getting-started
, digite
$ chmod +x gradlew $ ./gradlew generateProto
As classes a seguir são geradas da nossa definição de serviço (em build/generated/sources/proto/main/java
):
- Um para cada tipo de mensagem:
Feature.java
,Rectangle.java, ...
, que contêm todo o código de buffer de protocolo para preencher, serializar e recuperar nossos tipos de mensagens de solicitação e resposta. RouteGuideGrpc.java
, que contém (junto com outros códigos úteis) uma classe base para os servidoresRouteGuide
implementarem,RouteGuideGrpc.RouteGuideImplBase
, com todos os métodos definidos no serviçoRouteGuide
e classes de stub para os clientes usarem.
5. Implementar o serviço
Primeiro, vamos ver como criar um servidor RouteGuide
. Há duas partes para fazer nosso serviço RouteGuide
funcionar:
- Implementar a interface de serviço gerada da nossa definição de serviço: fazer o "trabalho" real do nosso serviço.
- Executar um servidor gRPC para detectar solicitações de clientes e enviá-las à implementação de serviço correta.
Implementar o RouteGuide
Vamos implementar uma classe RouteGuideService
que vai estender a classe RouteGuideGrpc.RouteGuideImplBase gerada. Veja como ficaria a implementação.
public void listFeatures(Rectangle request, StreamObserver<Feature> responseObserver) {
...
}
public StreamObserver<Point> recordRoute(final StreamObserver<RouteSummary> responseObserver) {
...
}
public StreamObserver<RouteNote> routeChat(final StreamObserver<RouteNote> responseObserver) {
...
}
Vamos analisar cada implementação de RPC em detalhes.
RPC de streaming do lado do servidor
Agora vamos analisar uma das nossas RPCs de streaming. ListFeatures
é uma RPC de streaming do lado do servidor. Portanto, precisamos enviar vários Features
de volta ao cliente.
private final Collection<Feature> features;
@Override
public void listFeatures(Rectangle request, StreamObserver<Feature> responseObserver) {
int left = min(request.getLo().getLongitude(), request.getHi().getLongitude());
int right = max(request.getLo().getLongitude(), request.getHi().getLongitude());
int top = max(request.getLo().getLatitude(), request.getHi().getLatitude());
int bottom = min(request.getLo().getLatitude(), request.getHi().getLatitude());
for (Feature feature : features) {
if (!RouteGuideUtil.exists(feature)) {
continue;
}
int lat = feature.getLocation().getLatitude();
int lon = feature.getLocation().getLongitude();
if (lon >= left && lon <= right && lat >= bottom && lat <= top) {
responseObserver.onNext(feature);
}
}
responseObserver.onCompleted();
}
Assim como o RPC simples, esse método recebe um objeto de solicitação (o Rectangle
em que nosso cliente quer encontrar Features
) e um observador de resposta StreamObserver
.
Desta vez, recebemos quantos objetos Feature
forem necessários para retornar ao cliente. Neste caso, selecionamos eles da coleção de recursos do serviço com base em se estão dentro da nossa solicitação Rectangle
e os gravamos um de cada vez no observador de resposta usando o método onNext()
. Por fim, como no nosso RPC simples, usamos o método onCompleted()
do observador de resposta para informar ao gRPC que terminamos de gravar respostas.
RPC de streaming do lado do cliente
Agora vamos analisar algo um pouco mais complicado: o método de streaming do lado do cliente RecordRoute()
, em que recebemos um fluxo de Points
do cliente e retornamos um único RouteSummary
com informações sobre a viagem.
@Override
public StreamObserver<Point> recordRoute(final StreamObserver<RouteSummary> responseObserver) {
return new StreamObserver<Point>() {
int pointCount;
int featureCount;
int distance;
Point previous;
long startTime = System.nanoTime();
@Override
public void onNext(Point point) {
pointCount++;
if (RouteGuideUtil.exists(checkFeature(point))) {
featureCount++;
}
// For each point after the first, add the incremental distance from the previous point
// to the total distance value.
if (previous != null) {
distance += calcDistance(previous, point);
}
previous = point;
}
@Override
public void onError(Throwable t) {
logger.log(Level.WARNING, "Encountered error in recordRoute", t);
}
@Override
public void onCompleted() {
long seconds = NANOSECONDS.toSeconds(System.nanoTime() - startTime);
responseObserver.onNext(RouteSummary.newBuilder().setPointCount(pointCount)
.setFeatureCount(featureCount).setDistance(distance)
.setElapsedTime((int) seconds).build());
responseObserver.onCompleted();
}
};
}
Como você pode ver, assim como os tipos de métodos anteriores, nosso método recebe um parâmetro StreamObserver
responseObserver
, mas desta vez ele retorna um StreamObserver
para o cliente gravar o Points
.
No corpo do método, instanciamos um StreamObserver
anônimo para retornar, em que:
- Substitua o método
onNext()
para receber recursos e outras informações sempre que o cliente gravar umPoint
no fluxo de mensagens. - Substitua o método
onCompleted()
(chamado quando o cliente termina de escrever mensagens) para preencher e criar nossoRouteSummary
. Em seguida, chamamos oonNext()
do observador de resposta do nosso método com nossoRouteSummary
e chamamos o métodoonCompleted()
para concluir a chamada do lado do servidor.
RPC de streaming bidirecional
Por fim, vamos analisar nossa RPC de streaming bidirecional RouteChat()
.
@Override
public StreamObserver<RouteNote> routeChat(final StreamObserver<RouteNote> responseObserver) {
return new StreamObserver<RouteNote>() {
@Override
public void onNext(RouteNote note) {
List<RouteNote> notes = getOrCreateNotes(note.getLocation());
// Respond with all previous notes at this location.
for (RouteNote prevNote : notes.toArray(new RouteNote[0])) {
responseObserver.onNext(prevNote);
}
// Now add the new note to the list
notes.add(note);
}
@Override
public void onError(Throwable t) {
logger.log(Level.WARNING, "Encountered error in routeChat", t);
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
Assim como no exemplo de streaming do lado do cliente, recebemos e retornamos um StreamObserver
, mas desta vez retornamos valores pelo observador de resposta do nosso método enquanto o cliente ainda está gravando mensagens no fluxo de mensagens dele. A sintaxe para leitura e gravação aqui é exatamente a mesma dos nossos métodos de streaming de cliente e de servidor. Embora cada lado sempre receba as mensagens do outro na ordem em que foram escritas, o cliente e o servidor podem ler e gravar em qualquer ordem. Os streams operam de forma completamente independente.
Inicie o servidor
Depois de implementar todos os métodos, também precisamos iniciar um servidor gRPC para que os clientes possam usar nosso serviço. O snippet a seguir mostra como fazemos isso para nosso serviço RouteGuide
:
public RouteGuideServer(int port, URL featureFile) throws IOException {
this(ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile));
}
/** 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();
}
public void start() throws IOException {
server.start();
logger.info("Server started, listening on " + port);
}
Como você pode ver, criamos e iniciamos nosso servidor usando um ServerBuilder
.
Para fazer isso, nós:
- Especifique o endereço e a porta que queremos usar para detectar solicitações do cliente usando o método
forPort()
do builder. - Crie uma instância da classe de implementação do serviço
RouteGuideService
e transmita-a ao métodoaddService()
do builder. - Chame
build()
estart()
no builder para criar e iniciar um servidor RPC para nosso serviço.
Como o ServerBuilder já incorpora a porta, o único motivo para transmitir uma porta é usá-la para geração de registros.
6. Criar o cliente
Nesta seção, vamos criar um cliente para nosso serviço RouteGuide
. Confira o exemplo completo de código do cliente em ../complete/src/main/java/io/grpc/complete/routeguide/
RouteGuideClient.java
.
Instanciar um stub
Para chamar métodos de serviço, primeiro precisamos criar um stub, ou melhor, dois stubs:
- um stub de bloqueio/síncrono: isso significa que a chamada de RPC aguarda a resposta do servidor e retorna uma resposta ou gera uma exceção.
- um stub não bloqueador/assíncrono que faz chamadas não bloqueadoras para o servidor, em que a resposta é retornada de forma assíncrona. Você só pode fazer determinados tipos de chamadas de streaming usando um stub assíncrono.
Primeiro, precisamos criar um canal gRPC para nosso stub, especificando o endereço do servidor e a porta a que queremos nos conectar:
public static void main(String[] args) throws InterruptedException {
String target = "localhost:8980";
if (args.length > 0) {
if ("--help".equals(args[0])) {
System.err.println("Usage: [target]");
System.err.println("");
System.err.println(" target The server to connect to. Defaults to " + target);
System.exit(1);
}
target = args[0];
}
List<Feature> features;
try {
features = RouteGuideUtil.parseFeatures(RouteGuideUtil.getDefaultFeaturesFile());
} catch (IOException ex) {
ex.printStackTrace();
return;
}
ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create())
.build();
try {
RouteGuideClient client = new RouteGuideClient(channel);
// Looking for features between 40, -75 and 42, -73.
client.listFeatures(400000000, -750000000, 420000000, -730000000);
// Record a few randomly selected points from the features file.
client.recordRoute(features, 10);
// Send and receive some notes.
CountDownLatch finishLatch = client.routeChat();
if (!finishLatch.await(1, TimeUnit.MINUTES)) {
client.warning("routeChat did not finish within 1 minutes");
}
} finally {
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
Usamos um ManagedChannelBuilder
para criar o canal.
Agora podemos usar o canal para criar nossos stubs usando os métodos newStub
e newBlockingStub
fornecidos na classe RouteGuideGrpc
que geramos do nosso .proto
.
public RouteGuideClient(Channel channel) {
blockingStub = RouteGuideGrpc.newBlockingStub(channel);
asyncStub = RouteGuideGrpc.newStub(channel);
}
Lembre-se de que, se não for bloqueador, será assíncrono.
Chamar métodos de serviço
Agora vamos ver como chamamos os métodos do serviço. Todas as RPCs criadas do stub de bloqueio operam em um modo de bloqueio/síncrono, o que significa que a chamada de RPC aguarda a resposta do servidor e retorna uma resposta ou um erro.
RPC de streaming do lado do servidor
Agora, vamos analisar uma chamada de streaming do lado do servidor para ListFeatures
, que retorna um fluxo de Feature
geográficos:
Rectangle request = Rectangle.newBuilder()
.setLo(Point.newBuilder().setLatitude(lowLat).setLongitude(lowLon).build())
.setHi(Point.newBuilder().setLatitude(hiLat).setLongitude(hiLon).build()).build();
Iterator<Feature> features;
try {
features = blockingStub.listFeatures(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
Como você pode ver, ele é muito semelhante ao RPC unário simples que vimos no codelab Getting_Started_With_gRPC_Java. A diferença é que, em vez de retornar um único Feature
, o método retorna um Iterator
que o cliente pode usar para ler todos os Features
retornados.
RPC de streaming do lado do cliente
Agora, algo um pouco mais complicado: o método de streaming do lado do cliente RecordRoute
, em que enviamos um stream de Points
para o servidor e recebemos um único RouteSummary
. Para esse método, precisamos usar o stub assíncrono. Se você já leu Como criar o servidor, algumas partes podem parecer muito familiares. As RPCs de streaming assíncronas são implementadas de maneira semelhante nos dois lados.
public void recordRoute(List<Feature> features, int numPoints) throws InterruptedException {
info("*** RecordRoute");
final CountDownLatch finishLatch = new CountDownLatch(1);
StreamObserver<RouteSummary> responseObserver = new StreamObserver<RouteSummary>() {
@Override
public void onNext(RouteSummary summary) {
info("Finished trip with {0} points. Passed {1} features. "
+ "Travelled {2} meters. It took {3} seconds.", summary.getPointCount(),
summary.getFeatureCount(), summary.getDistance(), summary.getElapsedTime());
}
@Override
public void onError(Throwable t) {
Status status = Status.fromThrowable(t);
logger.log(Level.WARNING, "RecordRoute Failed: {0}", status);
finishLatch.countDown();
}
@Override
public void onCompleted() {
info("Finished RecordRoute");
finishLatch.countDown();
}
};
StreamObserver<Point> requestObserver = asyncStub.recordRoute(responseObserver);
try {
// Send numPoints points randomly selected from the features list.
Random rand = new Random();
for (int i = 0; i < numPoints; ++i) {
int index = rand.nextInt(features.size());
Point point = features.get(index).getLocation();
info("Visiting point {0}, {1}", RouteGuideUtil.getLatitude(point),
RouteGuideUtil.getLongitude(point));
requestObserver.onNext(point);
// Sleep for a bit before sending the next one.
Thread.sleep(rand.nextInt(1000) + 500);
if (finishLatch.getCount() == 0) {
// RPC completed or errored before we finished sending.
// Sending further requests won't error, but they will just be thrown away.
return;
}
}
} catch (RuntimeException e) {
// Cancel RPC
requestObserver.onError(e);
throw e;
}
// Mark the end of requests
requestObserver.onCompleted();
// Receiving happens asynchronously
finishLatch.await(1, TimeUnit.MINUTES);
}
Como você pode ver, para chamar esse método, precisamos criar um StreamObserver
, que implementa uma interface especial para o servidor chamar com a resposta RouteSummary
. No nosso StreamObserver
, vamos:
- Substitua o método
onNext()
para imprimir as informações retornadas quando o servidor gravar umRouteSummary
no fluxo de mensagens. - Substitua o método
onCompleted()
(chamado quando o servidor conclui a chamada do lado dele) para reduzir umCountDownLatch
e verificar se o servidor terminou de gravar.
Em seguida, transmitimos o StreamObserver
ao método recordRoute()
do stub assíncrono e recebemos nosso próprio observador de solicitação StreamObserver
para gravar nosso Points
e enviar ao servidor. Depois de terminar de gravar pontos, usamos o método onCompleted()
do observador de solicitação para informar ao gRPC que terminamos de gravar no lado do cliente. Depois disso, verificamos nosso CountDownLatch
para saber se o servidor concluiu a tarefa.
RPC de streaming bidirecional
Por fim, vamos analisar nossa RPC de streaming bidirecional RouteChat()
.
public CountDownLatch routeChat() {
info("*** RouteChat");
final CountDownLatch finishLatch = new CountDownLatch(1);
StreamObserver<RouteNote> requestObserver =
asyncStub.routeChat(new StreamObserver<RouteNote>() {
@Override
public void onNext(RouteNote note) {
info("Got message \"{0}\" at {1}, {2}", note.getMessage(), note.getLocation()
.getLatitude(), note.getLocation().getLongitude());
}
@Override
public void onError(Throwable t) {
warning("RouteChat Failed: {0}", Status.fromThrowable(t));
finishLatch.countDown();
}
@Override
public void onCompleted() {
info("Finished RouteChat");
finishLatch.countDown();
}
});
try {
RouteNote[] requests =
{newNote("First message", 0, 0), newNote("Second message", 0, 10_000_000),
newNote("Third message", 10_000_000, 0), newNote("Fourth message", 10_000_000, 10_000_000)};
for (RouteNote request : requests) {
info("Sending message \"{0}\" at {1}, {2}", request.getMessage(), request.getLocation()
.getLatitude(), request.getLocation().getLongitude());
requestObserver.onNext(request);
}
} catch (RuntimeException e) {
// Cancel RPC
requestObserver.onError(e);
throw e;
}
// Mark the end of requests
requestObserver.onCompleted();
// return the latch while receiving happens asynchronously
return finishLatch;
}
Assim como no exemplo de streaming do lado do cliente, recebemos e retornamos um observador de resposta StreamObserver
. No entanto, desta vez, enviamos valores pelo observador de resposta do nosso método enquanto o servidor ainda está gravando mensagens no fluxo de mensagens dele. A sintaxe para leitura e gravação aqui é exatamente a mesma do nosso método de streaming do cliente. Embora cada lado sempre receba as mensagens do outro na ordem em que foram escritas, o cliente e o servidor podem ler e gravar em qualquer ordem. Os streams operam de forma completamente independente.
7. Faça um teste
- No diretório
start_here
:
$ ./gradlew installDist
Isso vai compilar seu código, empacotá-lo em um jar e criar os scripts que executam o exemplo. Eles serão criados no diretório build/install/start_here/bin/
. Os scripts são: route-guide-server
e route-guide-client
.
O servidor precisa estar em execução antes de iniciar o cliente.
- Execute o servidor:
$ ./build/install/start_here/bin/route-guide-server
- Execute o cliente:
$ ./build/install/start_here/bin/route-guide-client
8. A seguir
- Saiba como o gRPC funciona em Introdução ao gRPC e Conceitos principais.
- Siga as etapas do tutorial de noções básicas.
- Confira a referência da API.