簡介:
grpc是谷歌的一個開源的rpc(遠端服務呼叫)框架,可以讓各個語言按照指定的規則通過http2協議相互呼叫,這個規則是用Protocol Buffer(谷歌的一個資料描述語言)寫的一個.proto檔案,grpc的目的就是為了讓服務呼叫更方便。
目前支援的語言有C, C++,C#,Java, Node.js, Python,Go等,大部分語言都是通過外掛根據.proto檔案生成對應的程式碼,用生成好的程式碼,建立或呼叫grpc服務。
這是grpc的官方文件
grpc的介面呼叫分為四類
1.普通呼叫
2.請求流呼叫
3.響應流呼叫
4.雙向流呼叫
從.proto檔案開始
常用的關鍵字
syntax | 指定語言版本 |
option | 修改配置選項 |
service | 宣告一個服務 |
rpc | 宣告一個方法 |
resturns | 方法的返回值 |
message | 定義一個訊息型別 |
repeated | 陣列 |
stream | 用流來互動 |
這是proto的語法教程
一個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | syntax = "proto3" ; option java_package = "java_test" ; option java_multiple_files = true ; service TestService { rpc method(Request) returns (Result){} } message Request { string request1 = 1 ; string request2 = 2 ; } message Result { string result1 = 1 ; string result2 = 2 ; } |
指定一個版本:
針對java的程式碼生成的一些配置
1 2 | option java_package = "java_test" ; option java_multiple_files = true ; |
用 message 定義了一個請求訊息,和一個返回訊息
1 2 3 4 5 6 7 8 9 10 11 | message Request { string request1 = 1 ; string request2 = 2 ; } message Result { string result1 = 1 ; string result2 = 2 ; } |
用 service 宣告瞭一個服務,用 rpc 宣告一個方法
1 2 3 4 | service TestService { rpc method(Request) returns (Result){} } |
說正經的:
想使用grpc要先做一些配置
新增grpc的包
1 2 3 4 5 | < dependency > < groupId >io.grpc</ groupId > < artifactId >grpc-all</ artifactId > < version >1.10.1</ version > </ dependency > |
新增編譯.proto檔案用的外掛
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | < plugin > < groupId >org.xolstice.maven.plugins</ groupId > < artifactId >protobuf-maven-plugin</ artifactId > < version >0.5.0</ version > < configuration > < protocArtifact >com.google.protobuf:protoc:3.0.0-beta-4:exe:${os.detected.classifier}</ protocArtifact > < pluginArtifact >io.grpc:protoc-gen-grpc-java:0.15.0:exe:${os.detected.classifier}</ pluginArtifact > < pluginId >grpc</ pluginId > < protoSourceRoot >src/main/resources/proto</ protoSourceRoot > </ configuration > < executions > < execution > < goals > < goal >compile</ goal > < goal >compile-custom</ goal > </ goals > </ execution > </ executions > </ plugin > |
新增.proto檔案的編譯工具
1 2 3 4 5 6 | < configuration > < protocArtifact >com.google.protobuf:protoc:3.0.0-beta-4:exe:${os.detected.classifier}</ protocArtifact > < pluginArtifact >io.grpc:protoc-gen-grpc-java:0.15.0:exe:${os.detected.classifier}</ pluginArtifact > < pluginId >grpc</ pluginId > < protoSourceRoot >src/main/resources/proto</ protoSourceRoot > </ configuration > |
protoc工具通過.proto檔案生成對應的java對應的類
1 | < protocArtifact >com.google.protobuf:protoc:3.0.0-beta-4:exe:${os.detected.classifier}</ protocArtifact > |
protoc-gen-grpc-java工具通過.proto檔案生成grpc的工具類
1 | < pluginArtifact >io.grpc:protoc-gen-grpc-java:0.15.0:exe:${os.detected.classifier}</ pluginArtifact > |
這是生成grpc工具類存放的資料夾的名字
1 | < pluginId >grpc</ pluginId > |
要編輯的.proto檔案的路徑
1 | < protoSourceRoot >src/main/resources/proto</ protoSourceRoot > |
這個是為下載上面工具用的,他可以提供一些變數,
os.detected.classifier變數可以根據當前系統的型別來下載對應的工具
1 2 3 4 5 | < extension > < groupId >kr.motd.maven</ groupId > < artifactId >os-maven-plugin</ artifactId > < version >1.4.1.Final</ version > </ extension > |
這是上面兩個編譯工具用到的命令,當用maven編譯專案時會執行這兩個命令
1 2 | < goal >compile</ goal > < goal >compile-custom</ goal > |
真的,說正經的:
用maven編譯一下
會生成兩個資料夾
java資料夾是protoc編譯工具生成的程式碼
grpc資料夾是protoc-gen-grpc-java編譯工具生成的工具類
這兩個檔案就是我們在.proto檔案中定義的訊息型別(經常被用到)
這兩個是為訊息型別的一個介面,裡面有get方法(不會被用到)
這個是對訊息的一個描述(更不會被用到)
這個是grpc的工具類(會被用到)
這次真的要說正經的了,我們要用這些grpc為我們生成出來的奇怪的東西,寫奇怪的東西了:
1.普通介面
1.1.服務端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | package com.gutousu.grpc_service_java_test.service; import io.grpc.ServerBuilder; import io.grpc.stub.StreamObserver; import java_test.Request; import java_test.Result; import java_test.TestServiceGrpc; import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Component; @Component public class JavaGrpcServer extends TestServiceGrpc.TestServiceImplBase implements InitializingBean { @Override public void method(Request request, StreamObserver<Result> responseObserver) { Result result = Result.newBuilder().setResult1( "result1" ).setResult2( "result2" ).build(); responseObserver.onNext(result); responseObserver.onCompleted(); } @Override public void afterPropertiesSet() throws Exception { ServerBuilder.forPort( 2 ) .addService( new JavaGrpcServer()) .build() .start(); } } |
首先建立一個服務類叫JavaGrpcServer 繼承 TestServiceGrpc.TestServiceImplBase 重寫裡面的method方法
1 | public class JavaGrpcServer extends TestServiceGrpc.TestServiceImplBase |
TestServiceGrpc.TestServiceImplBase 就是我們在.proto檔案中定義的服務
用 ServerBuilder 的 forProt 方法來指定一個埠,用 addService 來新增一個服務類,也就是當前類
1 2 3 4 | ServerBuilder.forPort( 2 ) .addService( new JavaGrpcServer()) .build() .start(); |
grpc生成的訊息類有點獨特,他們沒有set方法,只有get方法,想要賦值,要用他們的一個內部類Builder來間接賦值
1 | Result result = Result.newBuilder().setResult1( "result1" ).setResult2( "result2" ).build(); |
新增返回值,完成呼叫
1 2 | responseObserver.onNext(result); responseObserver.onCompleted(); |
StreamObserver(流觀察者) 這個介面會在後面詳細說,這裡只需要知道 onNext 是新增返回值,onCompleted 是完成呼叫即可
這裡利用了spring的 InitializingBean 介面和 Component 註解在bean初始化的時候建立服務
好了,服務端搞完了,下一個
1.2.客戶端
先寫一個叫 Functional 的函式式介面,方便呼叫
1 2 3 4 5 6 | package com.gutousu.grpc_client_java_test; public interface Functional<Arg,Result> { Result run(Arg arg); } |
建一個叫 JavaGrpcClient 的類 來呼叫介面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | package com.gutousu.grpc_client_java_test.client; import com.gutousu.grpc_client_java_test.Functional; import io.grpc.Channel; import io.grpc.ManagedChannelBuilder; import java_test.TestServiceGrpc; import org.springframework.stereotype.Component; @Component public class JavaGrpcClient { private Channel channel = channel(); public <Result> Result run(Functional<TestServiceGrpc.TestServiceBlockingStub,Result> functional) { TestServiceGrpc.TestServiceBlockingStub testServiceBlockingStub = TestServiceGrpc.newBlockingStub(channel); return functional.run(testServiceBlockingStub); } private Channel channel() { return ManagedChannelBuilder .forAddress( "192.168.0.31" , 2 ) .usePlaintext( true ) .build(); } } |
用 ManagedChannelBuilder 的 forAddress 方法來連線服務端,usePlaintext的意思是使用明文不加密(應該可以加密)
1 2 3 4 5 6 7 | private Channel channel() { return ManagedChannelBuilder .forAddress( "192.168.0.31" , 2 ) .usePlaintext( true ) .build(); } |
用 TestServiceGrpc.newBlockingStub 來建立一個例項
1 2 | TestServiceGrpc.TestServiceBlockingStub testServiceBlockingStub = TestServiceGrpc.newBlockingStub(channel); |
再搞一個測試
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package com.gutousu.grpc_client_java_test; import com.gutousu.grpc_client_java_test.client.JavaGrpcClient; import java_test.Request; import java_test.Result; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith (SpringRunner. class ) @SpringBootTest public class GrpcClientJavaTestApplicationTests { @Autowired private JavaGrpcClient javaGrpcClient; @Test public void contextLoads() { Request request = Request.newBuilder().setRequest1( "test1" ).setRequest2( "test2" ).build(); Result result = javaGrpcClient.run(o -> o.method(request)); } } |
讓我們把這兩個專案跑起來,看一下
看!斷點經過了建立服務那裡,而且沒有報錯,服務端跑起來了!
看!客戶端要!
他進來了,連線了服務端,建立了例項,馬上就要....
他帶著引數過來了,被斷點攔住了
給他一個返回值,結束
走你!
拿到了返回值,完結!撒花!
等等!
這只是普通的介面
還有三種介面呢!