介绍(what)
gRPC 是一个高性能、开源和通用的 RPC(远程过程调用协议) 框架。主要思想是:定义一个服务, 指定其可以被远程调用的方法及其参数和返回类型。用来实现不同进程间的通信。
上图为gRPC工作流程图,主要从两个方面来理解该流程.
1.图中服务端使用的是C++语言开发的系统,客户端分别使用的是Android-Java开发的平台和Ruby语言开发的平台.gRPC可以在不同的语言开发的平台上使用.
2.主要实现的流程为,在服务端实现自身定义的服务内的接口,并运行一个 gRPC 服务器来处理客户端的请求调用并将处理结果返回到客户端。在客户端拥有一个存根能够像服务端一样的方法。
使用(how)
具体怎么实现gRPC的工作流程呢?(How),实现之前需要先了解gRPC 默认使用 protocol buffers接口定义语言(IDL)可以类比AIDL--Android平台上的接口定义语言,也是用来定义不同进程间通信的接口
.
step 1————–在一个 .proto 文件内定义服务。
//此文件为Hello.proto(类似于Android中的*.aidl文件)
syntax = "proto3";
//可以定义四种service,请求的消息类型和返回的消息类型一般是在此文件中定义message.
service IFourKind{
//第一种service,一个 简单 RPC,客户端使用存根发送请求到服务器并等待响应返回,就像平常的函数调用一样。
rpc GetARespone(ARequest) returns (ARespone) {}
//第二种service,一个服务器端流式RPC,客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。
rpc ListBRespone(ARequest) returns (stream ARespone) {}
//第三种service,一个客户端流式RPC,客户端写入一个消息序列并将其发送到服务器,同样也是使用流。一旦 客户端完成写入消息,它等待服务器完成读取返回它的响应。
rpc ListCRespone(stream ARequest) returns (ARespone) {}
//第四种service,一个双向流式 RPC 是双方使用读写流去发送一个消息序列。两个流独立操作,因此客户端和服务器 可以以任意喜欢的顺序读写
rpc ListDRespone(stream ARequest) returns (stream ARespone) {}
//请求类型
message ARequest{
...//成员变量,参考protocol buffers数据格式的定义
}
//回应类型
message ARespone{
...//成员变量,参考protocol buffers数据格式的定义
}
step 2————–用 protocol buffer 编译器生成指定编程语言的服务器和客户端代码。
通过 protocol buffer 的编译器 protoc 或者一个protoc的IDEA插件来完成。生成.proto文件中定义的接口类和message消息类型的类文件.
step 3————–使用 gRPC 的 对应的语言的 API 实现服务端和客户端的进程间的通信.
服务端的主要工作
1.实现定义的服务接口函数.
2.开启服务端,监听来自客户端的请求并响应客户端的请求.
//1.实现定义的服务IFourKind接口函数.
///第一种service
@Override
public void GetARespone(ARequest request,StreamObserver<ARespone> responseObserver) {
//ARequest为客户端请求的消息,StreamObserver<ARespone>为服务端响应客户端的接口
ARespone response=........(省略的代码为根据客户端请求做出处理,生成回应的类型的对象)
responseObserver.onNext(response);//返回给客户端,具体看怎么返回需要查看源码
responseObserver.onCompleted();//服务端完成客户端请求回应的回调函数.
}
///第二种service同第一种
@Override
public void ListBRespone(ARequest request, StreamObserver<ARespone> responseObserver) {
//ARequest为客户端请求的消息,StreamObserver<ARespone>为服务端响应客户端的接口
ARespone response=........(省略的代码为根据客户端请求做出处理,生成回应的类型的对象)
responseObserver.onNext(response);//返回给客户端,具体看怎么返回需要查看源码
responseObserver.onCompleted();//服务端完成客户端请求回应的回调函数.
responseObserver.onCompleted();
}
///第三种service
@Override
public StreamObserver<ARequest> ListCRespone(final StreamObserver<ARespone> responseObserver) {
return new StreamObserver<ARequest>() {
@Override
public void onNext(ARequest request) {
//从客户端不断接受ARequest流
//每接收到可以对ARequest进行处理
..........
}
@Override
public void onError(Throwable t) {
logger.log("error------");
}
//客户端发送请求流结束
@Override
public void onCompleted() {
ARespone response=........(省略的代码为根据客户端请求做出处理,生成回应的类型的对象)
responseObserver.onNext(response);
responseObserver.onCompleted();
}
};
}
///第四种service
@Override
public StreamObserver<ARequest> ListDRespone(final StreamObserver<ARespone> responseObserver) {
return new StreamObserver<ARequest>() {
@Override
public void onNext(ARequest request) {
//从客户端不断接受ARequest流
//每接收到可以对ARequest进行处理,.
//处理完可以立马responseObserver.onNext(response)回应给客户端;
..........
}
@Override
public void onError(Throwable t) {
logger.log("error------");
}
//客户端发送请求流结束
@Override
public void onCompleted() {
ARespone response=........(省略的代码为根据客户端请求做出处理,生成回应的类型的对象)
responseObserver.onNext(response);
responseObserver.onCompleted();
}
};
}
--------------------------------------------------------------------------
//2.开启服务端,监听来自客户端的请求并响应客户端的请求.
public void start() {
//绑定端口,添加service,启动服务
gRpcServer = NettyServerBuilder.forPort(port)
.addService(IFourKindGrpc.bindService(new IFourKindService()))
.build().start();
logger.info("Server started, listening on " + port);
...
}
客户端的主要工作
1.创建Client实例并连接 server.
2.调用服务端方法(请求)并获取服务端回应.
//1.创建gRPC channel,根据端口和IP连接服务端
channel = NettyChannelBuilder.forAddress(host, port)
.negotiationType(NegotiationType.PLAINTEXT)
.build();
//2.创建存根,存根是根据.proto文件中生成的类IFourKindGrpc的代理.blockingStub为阻塞式需要阻塞等待服务端的回应,而asyncStub为非阻塞可以异步执行.
blockingStub = RouteGuideGrpc.newBlockingStub(channel);
asyncStub = RouteGuideGrpc.newStub(channel);
//3.调用服务端的服务方法.相当于发送请求,并获取服务端的回应.
调用服务端的第一种service
ARequest request = .....//省略了构造请求对象的代码
ARespone respone = blockingStub.GetARespone(request); //获取服务端回应
调用服务端的第二种service
ARequest request = .....//省略了构造请求对象的代码
ARespone respone = blockingStub.ListBRespone(request); //获取服务端回应
调用服务端的第三种service
//a.构造接收服务端回应的操作接口.
StreamObserver<ARespone> responseObserver = new StreamObserver<ARespone>() {
@Override
public void onNext(ARespone summary) {
//接收到回应的处理do something
}
@Override
public void onError(Throwable t) {
//do something
}
@Override
public void onCompleted() {
//do something
}
};
//b.客户端创建请求对象的流操作接口.
StreamObserver<ARespone> requestObserver = asyncStub.ListCRespone(responseObserver);
//c.创建请求对象
ARequest request = .....//省略了构造请求对象的代码
//d.客户端发送请求流
requestObserver.onNext(request);
//e.完成请求
requestObserver.onCompleted();
调用服务端的第四种service ,方式同第三种
总结
1.本文主要讲的是gRPC的使用过程的细节,使用的是Java语言的例子.本文并没有深究自定义服务文件通过protoc自动生成的指定语言的类的代码.如果想了解服务端和客户端之间详细的回调过程还需要详细查阅自动生成的类RouteGuideGrpc里面的代码.在了解了该类的详细过程后,也可以不是有IDL文件来自动生成类,可以自己编写相应的类来完成进程间的通信的服务类.此块开发类似与Android开发中的不同进程的使用AIDL来通信
.
2.PRC的服务端和客户端间的通信基本问题还是不同进程间的通信,主要还是通过ip和端口号来标志不同的进程.从而通信的时候能找到对应的进程.