一步步完成gRPC 示例

  • 2019 年 10 月 4 日
  • 筆記

本文將一步步完成一個gRPC的示例。文章從如下幾個部分來加以說明:

  • 根據proto自動生成程式碼 - 編寫proto文件,並根據工具在window下自動生成gRPC所需程式碼
  • 程式碼組成 - 給出Maven工程的模組化結構組成,並在每個模組中一步步實現程式碼
  • 測試 - 對編寫的程式碼進行測試,包括Server啟動並綁定服務、Client連接並調用服務
  • 小結 - 針對本文的內容進行簡單的介紹

下面就一步步來完成每一個部分~

一、根據proto自動生成程式碼

1.1 定義服務介面

gRPC 默認使用 protocol buffers 作為介面定義語言,來描述服務介面和有效載荷消息結構。 更多的proto3文檔資訊,參考https://developers.google.com/protocol-buffers/docs/proto3

  • hello.proto
syntax = "proto3";    package com.xxx.tutorial.demo.grpc;    option java_multiple_files = true;  option java_package = "com.xxx.tutorial.demo.model";  option java_outer_classname = "Hello";    message HelloRequest{      string name  = 1;      int32 id    = 2;  }  message HelloResponse{      string message = 1;  }
  • hello_service.proto
syntax = "proto3";    package com.xxx.tutorial.demo.grpc;      option java_multiple_files = true;  option java_package = "com.xxx.tutorial.demo.service";  option java_outer_classname = "GreetingService";    import "hello.proto";    service HelloService{      rpc sayHello(HelloRequest) returns (HelloResponse);  }

1.2 產生proto文件對應程式碼

可以訪問網址【https://github.com/google/protobuf/releases】查看不同版本程式碼生成器,本文使用的版本為protoc-3.1.0-win32.zip,可以訪問如下地址下載,

【https://github.com/google/protobuf/releases/download/v3.1.0/protoc-3.1.0-win32.zip】

  • 下載好之後,解壓縮,並把proto文件防止到bin目錄中
  • 執行protoc.exe --java_out=./ *.proto生成程式碼
F:JavaDeveloperrpcgRpcprotoc-3.1.0-win32bin>protoc.exe --java_out=./ *.proto

執行上述命令之後,在bin目錄下會生成程式碼(包含包名), 如:

具體model和service包中的程式碼截圖如下:

注意:

上述生成了消息對象等相關程式碼,但是還不包含rpc服務所需要的通訊程式碼。

1.3 生成rpc通訊程式碼

rpc通訊程式碼需要protoc-gen-grpc-java 插件來完成,可以訪問如下網址下載:【protoc-gen-grpc-java-0.13.2-windows-x86_64.exe】

將下載的protoc-gen-grpc-java-0.13.2-windows-x86_64.exe放到protoc-3.1.0-win32下的bin目錄中。如:

執行如下語句即可~

protoc.exe --plugin=protoc-gen-grpc-java=protoc-gen-grpc-java-0.13.2-windows-x86_64.exe --grpc-java_out=./ *.proto

如:

F:JavaDeveloperrpcgRpcprotoc-3.1.0-win32bin>protoc.exe --java_out=./ *.proto  F:JavaDeveloperrpcgRpcprotoc-3.1.0-win32bin>protoc.exe --plugin=protoc-gen-grpc-java=protoc-gen-grpc-java-0.13.2-windows-x86_64.exe --grpc-java_out=./ *.proto  F:JavaDeveloperrpcgRpcprotoc-3.1.0-win32bin>

可以看到在service包中多了一個HelloServiceGrpc.java文件。

該文件在後續服務實現的時候需要用到。

至此,根據proto文件產生Java程式碼部分就完成了。接下來,要做的就是編寫服務實現、伺服器程式碼以及客戶端調用介面的程式碼等~

二、程式碼組成

2.1 Maven工程結構

grpc-demo這個Maven工程主要包含四個模組,

  • grpc-demo-interface - 存放proto文件產生的程式碼
  • grpc-demo-service - 實現服務
  • grpc-demo-server - 簡單伺服器
  • grpc-demo-client - 簡單的客戶端

pom文件如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <groupId>com.xxx.tutorial</groupId>    <artifactId>grpc-demo</artifactId>    <version>0.0.1-SNAPSHOT</version>    <packaging>pom</packaging>    <modules>      <module>grpc-demo-server</module>      <module>grpc-demo-client</module>      <module>grpc-demo-interface</module>      <module>grpc-demo-service</module>    </modules>  </project>

Eclipse的工程目錄結構如下:

2.2 grpc-demo-interface模組

直接將上述生成的程式碼,拷貝到src/main/java中,如

遇到「紅叉叉」是因為,沒有導入相關的jar包~ 在該模組下的pom.xml文件中添加相關的依賴包即可:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>      <groupId>com.xxx.tutorial</groupId>      <artifactId>grpc-demo</artifactId>      <version>0.0.1-SNAPSHOT</version>    </parent>    <artifactId>grpc-demo-interface</artifactId>      <dependencies>      <dependency>        <groupId>com.google.protobuf</groupId>        <artifactId>protobuf-java</artifactId>        <version>3.1.0</version>      </dependency>      <!-- https://mvnrepository.com/artifact/io.grpc/grpc-stub -->      <dependency>        <groupId>io.grpc</groupId>        <artifactId>grpc-stub</artifactId>        <version>1.3.0</version>      </dependency>      <dependency>        <groupId>io.grpc</groupId>        <artifactId>grpc-protobuf</artifactId>        <version>1.3.0</version>      </dependency>      <dependency>        <groupId>io.grpc</groupId>        <artifactId>grpc-netty</artifactId>        <version>1.3.0</version>      </dependency>    </dependencies>  </project>

服務介面模組搞定,接下來要完成介面的實現。

2.3 grpc-demo-service模組

  • 添加grpc-demo-interface依賴
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>      <groupId>com.xxx.tutorial</groupId>      <artifactId>grpc-demo</artifactId>      <version>0.0.1-SNAPSHOT</version>    </parent>    <artifactId>grpc-demo-service</artifactId>      <dependencies>      <dependency>        <groupId>com.xxx.tutorial</groupId>        <artifactId>grpc-demo-interface</artifactId>        <version>0.0.1-SNAPSHOT</version>      </dependency>    </dependencies>  </project>
  • 編寫服務介面實現類
package com.xxx.tutorial.demo.service.impl;    import java.util.logging.Logger;    import com.xxx.tutorial.demo.model.HelloRequest;  import com.xxx.tutorial.demo.model.HelloResponse;  import com.xxx.tutorial.demo.service.HelloServiceGrpc;    import io.grpc.stub.StreamObserver;    public class GreetingServiceImpl implements HelloServiceGrpc.HelloService {      private static final Logger logger = Logger.getLogger(GreetingServiceImpl.class.getName());      public void sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {        logger.info(String.format("sayHello方法調用的請求參數資訊: name={%s}, id={%d}", request.getName(), request.getId()));        HelloResponse reply = HelloResponse.newBuilder().setMessage(String.format("Hello, %s", request.getName()))          .build();        responseObserver.onNext(reply);      responseObserver.onCompleted();    }    }

2.4 grpc-demo-server模組

  • 添加grpc-demo-service依賴
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>      <groupId>com.xxx.tutorial</groupId>      <artifactId>grpc-demo</artifactId>      <version>0.0.1-SNAPSHOT</version>    </parent>    <artifactId>grpc-demo-server</artifactId>      <dependencies>      <dependency>        <groupId>com.xxx.tutorial</groupId>        <artifactId>grpc-demo-service</artifactId>        <version>0.0.1-SNAPSHOT</version>      </dependency>    </dependencies>    </project>
  • 編寫Server程式碼
package com.xxx.tutorial.demo.server;    import java.io.IOException;  import java.util.logging.Logger;    import com.xxx.tutorial.demo.service.HelloServiceGrpc;  import com.xxx.tutorial.demo.service.impl.GreetingServiceImpl;    import io.grpc.Server;  import io.grpc.ServerBuilder;    public class HelloServer {      private static final Logger logger = Logger.getLogger(HelloServer.class.getName());      private static final int DEFAULT_PORT = 50051;      private Server server = null;      private void start() throws IOException {        server = ServerBuilder.forPort(DEFAULT_PORT).addService(HelloServiceGrpc.bindService(new GreetingServiceImpl()))          .build().start();        logger.info("Server started, listening on " + DEFAULT_PORT);        Runtime.getRuntime().addShutdownHook(new Thread() {        @Override        public void run() {          System.err.println("*** shutting down gRPC server since JVM is shutting down");          HelloServer.this.stop();          System.err.println("*** server shut down");        }      });    }      private void stop() {      if (server != null) {        server.shutdown();      }    }      /**     * Await termination on the main thread since the grpc library uses daemon     * threads.     */    private void blockUntilShutdown() throws InterruptedException {      if (server != null) {        server.awaitTermination();      }    }      public static void main(String[] args) throws IOException, InterruptedException {      final HelloServer server = new HelloServer();      server.start();      server.blockUntilShutdown();    }    }

2.5 grpc-demo-client模組

  • 添加grpc-demo-interface依賴
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>      <groupId>com.xxx.tutorial</groupId>      <artifactId>grpc-demo</artifactId>      <version>0.0.1-SNAPSHOT</version>    </parent>    <artifactId>grpc-demo-service</artifactId>      <dependencies>      <dependency>        <groupId>com.xxx.tutorial</groupId>        <artifactId>grpc-demo-interface</artifactId>        <version>0.0.1-SNAPSHOT</version>      </dependency>    </dependencies>  </project>
  • 編寫Client
package com.xxx.tutorial.demo.client;    import java.util.concurrent.TimeUnit;  import java.util.logging.Logger;    import com.xxx.tutorial.demo.model.HelloRequest;  import com.xxx.tutorial.demo.model.HelloResponse;  import com.xxx.tutorial.demo.service.HelloServiceGrpc;    import io.grpc.ManagedChannel;  import io.grpc.netty.NegotiationType;  import io.grpc.netty.NettyChannelBuilder;    public class GreetingServiceClient {      private static final Logger logger = Logger.getLogger(GreetingServiceClient.class.getName());      private final ManagedChannel channel;      private final HelloServiceGrpc.HelloServiceBlockingStub blockingStub;      public GreetingServiceClient(String host, int port) {        channel = NettyChannelBuilder.forAddress(host, port).negotiationType(NegotiationType.PLAINTEXT).build();      blockingStub = HelloServiceGrpc.newBlockingStub(channel);    }      public void shutdown() throws InterruptedException {      channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);    }      public void sayHello(String name) {      try {        System.out.println("Will try to say Hello  " + name + " ...");        HelloRequest request = HelloRequest.newBuilder().setName(name).setId(12345678).build();        HelloResponse response = blockingStub.sayHello(request);        System.out.println("result from server: " + response.getMessage());      } catch (RuntimeException e) {        System.out.println("RPC failed:" + e.getMessage());        return;      }    }      public static void main(String[] args) throws Exception {        GreetingServiceClient client = new GreetingServiceClient("127.0.0.1", 50051);        try {        String name = "Eric";        logger.info(String.format("Client 調用RPC介面,參數為name = {%s}", name));        client.sayHello(name);      } finally {        client.shutdown();      }    }  }

至此,Maven工程中,4個模組的程式碼就簡單寫完了。接下來,測試一下。

三、測試

3.1 Server啟動

直接運行HelloServer.java(其中包含main函數)即可。

3.2 Client運行

直接運行GreetingServiceClient.java(其中包含main函數)即可。

3.3 結果查看

我們可以看到,客戶端調用介面,傳值name為Eric,得到服務端的相應為Hello, Eric。

五月 26, 2017 6:41:20 下午 com.xxx.tutorial.demo.client.GreetingServiceClient main  資訊: Client 調用RPC介面,參數為name = {Eric}  Will try to say Hello  Eric ...  result from server: Hello, Eric

再看一下,服務端的輸出日誌,也包含了請求參數,其中,id的值因為沒有用到,所以在Service實現的sayHello介面,直接設置了12345678。

五月 26, 2017 6:40:14 下午 com.xxx.tutorial.demo.server.HelloServer start  資訊: Server started, listening on 50051  五月 26, 2017 6:41:20 下午 com.xxx.tutorial.demo.service.impl.GreetingServiceImpl sayHello  資訊: sayHello方法調用的請求參數資訊: name={Eric}, id={12345678}

四、小結

從上面測試運行的結果可以看出,一個簡單的grpc示例就完成了~

其中, grpc支援四種RPC類型

  • 簡單RPC
  • 服務端流式RPC
  • 客戶端流式RPC
  • 雙向流式RPC

本示例完成的就是第一種「簡單RPC」的方式,客戶端發出單個請求,獲得單個響應。後續會對其它幾種RPC進行展示。

Tags: