之前介绍了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