一、gRPC簡介
在介紹gRPC之前先說一下RPC(Remote Procedure Call),也叫遠程過程調用協議,它是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的協議。相比HTTP協議來說,它主要是基於TCP/IP協議的的,傳輸效率更高,能夠跨語言,典型的RPC框架有RMI、Hessian、Dubbo等。想要深入了解它們之間區別的話可以看這篇博客。
gRPC由 google 開發,是一款語言中立、平台中立、開源的遠程過程調用(RPC)系統。具備以下特性:
- 基於HTTP/2,HTTP/2 提供了連接多路復用、雙向流、服務器推送、請求優先級、首部壓縮等機制。可以節省帶寬、降低TCP鏈接次數、節省CPU,幫助移動設備延長電池壽命等。gRPC 的協議設計上使用了HTTP2 現有的語義,請求和響應的數據使用HTTP Body 發送,其他的控制信息則用Header 表示。
- IDL使用ProtoBuf ,gRPC使用ProtoBuf來定義服務,ProtoBuf是由Google開發的一種數據序列化協議(類似於XML、JSON、hessian)。ProtoBuf能夠將數據進行序列化,並廣泛應用在數據存儲、通信協議等方面。壓縮和傳輸效率高,語法簡單,表達力強。
- 多語言支持(C, C++, Python, PHP, Nodejs, C#, Objective-C、Golang、Java),gRPC支持多種語言,並能夠基於語言自動生成客戶端和服務端功能庫。目前已提供了C版本grpc、Java版本grpc-java 和 Go版本grpc-go,其它語言的版本正在積極開發中,其中,grpc支持C、C++、Node.js、Python、Ruby、Objective-C、PHP和C#等語言,grpc-java已經支持Android開發。
二、認識protocol buffers
2.1簡介
protocol buffers(簡稱protobuf),是由google開源的,在 RPC (遠程方法調用)里非常流行的二進制編解碼格式,類似xml,json等。主要有以下幾個優點:
- 性能好/效率高
- 代碼生成機制,比如可以將proto文件編譯為Java文件
- 支持多種編程語言
- 支持“向后兼容”和“向前兼容”
2.2語法
2.2.1 定義一個消息類型
syntax = "proto3";
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;
}
第一行指定了使用proto3語法,如果沒有指定,編譯器會使用proto2。這個指定語法行必須是文件的非空非注釋的第一個行。
SearchRequest相當於我們Java中的一個實體類,里面有三個字段分別為String 類型query,int類型page_number和int類型result_per_page。
后面的數字1,2,3是標識號,必須從1開始。這些標識符是用來在消息的二進制格式中識別各個字段的,一旦開始使用就不能夠再改 變。注:[1,15]之內的標識號在編碼的時候會占用一個字節。[16,2047]之內的標識號則占用2個字節。所以應該為那些頻繁出現的消息元素保留 [1,15]之內的標識號
字段的第一個是修飾符,必須是required,optional,repeated其中一個。
- required:一個格式良好的消息一定要含有1個這種字段。表示該值是必須要設置的;
- optional:表示可選的,可以有值也可以沒有。
- repeated:在一個格式良好的消息中,這種字段可以重復任意多次(包括0次)。重復的值的順序會被保留。表示該值可以重復,相當於java中的List;
字段類型與Java類型對應關系如下圖:
2.2.2 定義一個服務(service)
如果想要將消息類型用在RPC(遠程方法調用)系統中,可以在.proto文件中定義一個RPC服務接口,protocol buffer編譯器將會根據所選擇的不同語言生成服務接口代碼及存根。如,想要定義一個RPC服務並具有一個方法,該方法能夠接收 SearchRequest並返回一個SearchResponse,此時可以在.proto文件中進行如下定義:
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
更多相關語法可以查看這篇博客
三、完成Hello World
項目結構如圖:
3.1新建項目
新建一個maven項目grpc_demo,然后在pom文件引入grpc和代碼生成插件,內容如下。
- <properties>
- <grpc.version>1.0.3</grpc.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>io.grpc</groupId>
- <artifactId>grpc-netty</artifactId>
- <version>${grpc.version}</version>
- </dependency>
- <dependency>
- <groupId>io.grpc</groupId>
- <artifactId>grpc-protobuf</artifactId>
- <version>${grpc.version}</version>
- </dependency>
- <dependency>
- <groupId>io.grpc</groupId>
- <artifactId>grpc-stub</artifactId>
- <version>${grpc.version}</version>
- </dependency>
- </dependencies>
- <build>
- <extensions>
- <extension>
- <groupId>kr.motd.maven</groupId>
- <artifactId>os-maven-plugin</artifactId>
- <version>1.4.1.Final</version>
- </extension>
- </extensions>
- <plugins>
- <plugin>
- <groupId>org.xolstice.maven.plugins</groupId>
- <artifactId>protobuf-maven-plugin</artifactId>
- <version>0.5.0</version>
- <configuration>
- <protocArtifact>com.google.protobuf:protoc:3.1.0:exe:${os.detected.classifier}</protocArtifact>
- <pluginId>grpc-java</pluginId>
- <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
- </configuration>
- <executions>
- <execution>
- <goals>
- <goal>compile</goal>
- <goal>compile-custom</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
3.2編寫proto文件
syntax = "proto3";
option java_multiple_files = true;
//定義包名
option java_package = "cn.sp.helloworld";
//定義生成的類名稱
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {} } // The request message containing the user's name. message HelloRequest { string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1; }
運行命令mvn clean compile,就可以看到編譯后生成的Java文件。
控制台輸出:
目錄結構
將這些代碼復制到src/main/java/cn/sp目錄下
3.3完成HelloWorldClient和HelloWorldServer
客戶端代碼
- package cn.sp.client;
- import cn.sp.GreeterGrpc;
- import cn.sp.HelloReply;
- import cn.sp.HelloRequest;
- import io.grpc.ManagedChannel;
- import io.grpc.ManagedChannelBuilder;
- import io.grpc.StatusRuntimeException;
- import java.util.concurrent.TimeUnit;
- public class HelloWorldClient {
- //一個gRPC信道
- private final ManagedChannel channel;
- private final GreeterGrpc.GreeterBlockingStub blockingStub;//阻塞/同步 存根
- //初始化信道和存根
- public HelloWorldClient(int port,String host){
- this(ManagedChannelBuilder.forAddress(host,port).usePlaintext(true));
- }
- private HelloWorldClient(ManagedChannelBuilder<?> channelBuilder){
- channel = channelBuilder.build();
- blockingStub = GreeterGrpc.newBlockingStub(channel);
- }
- public void shutDown()throws InterruptedException{
- channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
- }
- //客戶端方法
- public void greet(String name){
- HelloRequest request = HelloRequest.newBuilder().setName(name).build();
- HelloReply response;
- try{
- response = blockingStub.sayHello(request);
- }catch (StatusRuntimeException e){
- System.out.println("RPC調用失敗:"+e.getMessage());
- return;
- }
- System.out.println("服務器返回信息:"+response.getMessage());
- }
- public static void main(String[] args)throws Exception {
- HelloWorldClient client = new HelloWorldClient(50051,"127.0.0.1");
- try {
- for (int i=0;i<5;i++){
- client.greet("world:"+i);
- }
- }finally {
- client.shutDown();
- }
- }
- }
服務器端代碼
- package cn.sp.server;
- import cn.sp.GreeterGrpc;
- import cn.sp.HelloReply;
- import cn.sp.HelloRequest;
- import io.grpc.Server;
- import io.grpc.ServerBuilder;
- import io.grpc.stub.StreamObserver;
- public class HelloWorldServer {
- private int port = 50051;
- private Server server;
- /**
- * 啟動服務
- * @throws Exception
- */
- private void start()throws Exception{
- server = ServerBuilder.forPort(port)
- .addService(new GreeterImpl())
- .build().start();
- System.out.println("service start ....");
- Runtime.getRuntime().addShutdownHook(new Thread(){
- public void run() {
- System.err.println("*** shutting down gRPC server since JVM is shutting down");
- HelloWorldServer.this.stop();
- System.err.println("*** server shut down");
- }
- });
- }
- private void stop(){
- if (server != null){
- server.shutdown();
- }
- }
- // block 一直到程序退出
- private void blockUntilShutDown()throws InterruptedException{
- if (server != null){
- server.awaitTermination();
- }
- }
- // 定義一個實現服務接口的類
- private class GreeterImpl extends GreeterGrpc.GreeterImplBase{
- public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver){
- System.out.println("收到的信息:"+req.getName());
- //這里可以放置具體業務處理代碼 start
- //這里可以放置具體業務處理代碼 end
- //構造返回
- HelloReply reply = HelloReply.newBuilder().setMessage("Hello: " + req.getName()).build();
- responseObserver.onNext(reply);
- responseObserver.onCompleted();
- }
- }
- public static void main(String[] args)throws Exception {
- HelloWorldServer server = new HelloWorldServer();
- server.start();
- server.blockUntilShutDown();
- }
- }
3.4運行測試
先運行服務端的main方法,再運行客戶端,可以看到服務端控制台輸出信息如下:
service start ....
收到的信息:world:0
收到的信息:world:1
收到的信息:world:2
收到的信息:world:3
收到的信息:world:4
客戶端控制台輸出信息:
服務器返回信息:Hello: world:0
服務器返回信息:Hello: world:1
服務器返回信息:Hello: world:2
服務器返回信息:Hello: world:3
服務器返回信息:Hello: world:4
說明客戶端發出的五次問候請求服務端都接收到了,並且返回了響應。
四、總結
gRPC中proto文件的編寫就顯得十分重要了,要多加注釋相當於接口文檔,目前代碼調用過程看着貌似有些復雜,后面整合SpringBoot之后就很簡單了,不過我還是喜歡HTTP。。。。