一步步完成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進行展示。