能够比较使用google protobuf RPC实现echo service可见。述。算法
google protobuf仅仅负责消息的打包和解包。并不包括RPC的实现。但其包括了RPC的定义。若是有如下的RPC定义:网络
service MyService {
rpc Echo(EchoReqMsg) returns(EchoRespMsg)
}
那么要实现这个RPC需要最少作哪些事?总结起来需要完毕下面几步:函数
client
RPCclient需要实现google::protobuf::RpcChannel
。主要实现RpcChannel::CallMethod
接口。client调用不论什么一个RPC接口,终于都是调用到CallMethod
。这个函数的典型实现就是将RPC调用參数序列化,而后投递给网络模块进行发送。post
void CallMethod(const ::google::protobuf::MethodDescriptor* method,
::google::protobuf::RpcController* controller,
const ::google::protobuf::Message* request,
::google::protobuf::Message* response,
::google::protobuf::Closure* done) {
...
DataBufferOutputStream outputStream(...) // 取决于你使用的网络实现
request->SerializeToZeroCopyStream(&outputStream);
_connection->postData(outputStream.getData(), ...
...
}
服务端
服务端首先需要实现RPC接口。直接实现MyService
中定义的接口:ui
class MyServiceImpl : public MyService {
virtual void Echo(::google::protobuf::RpcController* controller,
const EchoReqMsg* request,
EchoRespMsg* response,
::google::protobuf::Closure* done) {
...
done->Run();
}
}
标示service&method
基于以上,可以看出服务端根本不知道client想要调用哪个RPC接口。google
从server接收到网络消息。到调用到MyServiceImpl::Echo
还有很是大一段距离。code
解决方法就是在网络消息中带上RPC接口标识。server
这个标识可以直接带上service name和method name,但这样的实现致使网络消息太大。还有一种实现是基于service name和method name生成一个哈希值,因为接口不会太多,因此较easy找到基本不冲突的字符串哈希算法。对象
无论哪一种方法,server是确定需要创建RPC接口标识到protobuf service对象的映射的。blog
这里提供第三种方法:基于option的方法。
protobuf中option机制相似于这样一种机制:service&method被视为一个对象,其有很是多属性,属性包括内置的,以及用户扩展的。用户扩展的就是option。每一个属性有一个值。protobuf提供訪问service&method这些属性的接口。
首先扩展service&method的属性。下面定义这些属性的key:
extend google.protobuf.ServiceOptions {
required uint32 global_service_id = 1000;
}
extend google.protobuf.MethodOptions {
required uint32 local_method_id = 1000;
}
应用层定义service&method时可以指定以上key的值:
service MyService
{
option (arpc.global_service_id) = 2302;
rpc Echo(EchoReqMsg) returns(EchoRespMsg)
{
option (arpc.local_method_id) = 1;
}
rpc Echo_2(EchoReqMsg) returns(EchoRespMsg)
{
option (arpc.local_method_id) = 2;
}
...
}
以上至关于在整个应用中。每个service都被赋予了惟一的id,单个service中的method也有惟一的id。
而后可以经过protobuf取出以上属性值:
void CallMethod(const ::google::protobuf::MethodDescriptor* method,
::google::protobuf::RpcController* controller,
const ::google::protobuf::Message* request,
::google::protobuf::Message* response,
::google::protobuf::Closure* done) {
...
google::protobuf::ServiceDescriptor *service = method->service();
uint32_t serviceId = (uint32_t)(service->options().GetExtension(global_service_id));
uint32_t methodId = (uint32_t)(method->options().GetExtension(local_method_id));
...
}
考虑到serviceId
methodId
的范围,可以直接打包到一个32位整数里:
uint32_t ret = (serviceId << 16) | methodId;
而后就可以把这个值做为网络消息头的一部分发送。
固然server端是需要创建这个标识值到service的映射的:
bool MyRPCServer::registerService(google::protobuf::Service *rpcService) {
const google::protobuf::ServiceDescriptor = rpcService->GetDescriptor();
int methodCnt = pSerDes->method_count();
for (int i = 0; i < methodCnt; i++) {
google::protobuf::MethodDescriptor *pMethodDes = pSerDes->method(i);
uint32_t rpcCode = PacketCodeBuilder()(pMethodDes); // 计算出映射值
_rpcCallMap[rpcCode] = make_pair(rpcService, pMethodDes); // 创建映射
}
return true;
}
服务端收到RPC调用后,取出这个标识值,而后再从_rpcCallMap
中取出相应的service和method,最后进行调用:
google::protobuf::Message* response = _pService->GetResponsePrototype(_pMethodDes).New();
// 用于回应的closure
RPCServerClosure *pClosure = new (nothrow) RPCServerClosure(
_channelId, _pConnection, _pReqMsg, pResMsg, _messageCodec, _version);
RPCController *pController = pClosure->GetRpcController();
...
// protobuf 生成的CallMethod,会本身主动调用到Echo接口
_pService->CallMethod(_pMethodDes, pController, _pReqMsg, pResMsg, pClosure);