之前介紹了Google的序列化反序列化工具protobuf。在protobuf的proto文件中除了可以定義message格式,還有一種類型時service。Google想通過service來實現rpc的功能,但是並沒有在protobuf中實現,而是開放給社區這個接口可以自己實現。同時Google開源了一個官方的實現grpc來生成對應的rpc調用
proto定義
首先在proto文件中定義想要的service
syntax = "proto3";
option java_package = "blog.proto";
message Person{
string my_name=1;
}
message Result{
string string=1;
}
service HelloService {
rpc hello(Person) returns (Result) {}
}
官方推薦在grpc中使用proto3,上面可以看到定義了一個HelloService,其下定義了hello方法,Person是入參,Result是出參。需要注意的是入參和出參無法使用簡單的數據類型不然會報 Expected message type.
編譯
proto文件是需要經過protoc來生成對應的開發語言的源碼的,在grpc中需要結合使用grpc的插件來實現proto文件中的service生成java服務端/客戶端文件。這里沿用之前的gradle插件
protobuf {
generatedFilesBaseDir = "$projectDir/src/"
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.2.0'
}
}
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
}
在protobuf的配置中加入grpc的插件並,運行generateProto之后就可以在src/main下看到一個新的grpc目錄,這個目錄中就是生成的service接口,生成的文件在客戶端和服務端都需要。注意,只有service的接口/類會生成在這個目錄,其他的message定義還是保持生成在原來的目錄。由於grpc目錄不是默認的sourceset,所以編譯無法找到對應的生成的java文件,不想每次編譯都手動增加目錄到編譯路徑,可以在gradle的build文件中將grpc默認加到sourceset中
sourceSets {
main {
java.srcDir 'src/main/grpc'
}
}
Server端
在Server端需要我們手動重寫service的實現並實現Server來啟動服務
//服務端的實現繼承生成的ImplBase類
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
@Override
public void hello(blog.proto.ProtoObj.Person request,
io.grpc.stub.StreamObserver<blog.proto.ProtoObj.Result> responseObserver) {
System.out.println(request.getMyName()+" calling");
//onNext返回值
responseObserver.onNext(ProtoObj.Result.newBuilder().setString("hello, "+request.getMyName()).build());
//服務結束
responseObserver.onCompleted();
}
}
//這是一個簡單的Server實現
public class HelloServer {
private int port;
private Server server;
public HelloServer(int port) throws IOException {
this.port=port;
//server的builder
server=ServerBuilder.forPort(port).addService(new HelloServiceImpl()).build();
//開始服務器
server.start();
System.out.println("Server started, listening on " + port );
}
private void blockUntilShutdown() throws InterruptedException {
while(true){
server.awaitTermination();
}
}
public static void main(String[] args) throws Exception {
//啟動8080端口並block線程
(new HelloServer(8080)).blockUntilShutdown();
}
}
之后運行main方法,服務就啟動了。
Client端
Client端在生成完java接口后可以構建Stub與服務器通訊
public class HelloClient {
public static void main(String[] args){
//grpc的channel
ManagedChannel channel=ManagedChannelBuilder.forAddress("127.0.0.1", 8080).usePlaintext(true).build();
//構建服務的stub
HelloServiceGrpc.HelloServiceBlockingStub stub= HelloServiceGrpc.newBlockingStub(channel);
ProtoObj.Person person=ProtoObj.Person.newBuilder().setMyName("World").build();
//調用方法
System.out.println(stub.hello(person).getString());
//關閉channel,不然服務端會報錯“遠程主機強迫關閉了一個現有的連接。”
channel.shutdown();
}
}
之后運行main方法就可以看到輸出hello, World
