基于protobuf的RPC实现

来源:互联网 发布:淘宝无线端视频要求 编辑:程序博客网 时间:2024/05/18 02:15

转:http://blog.csdn.net/kevinlynx/article/details/39379957

可以对照使用google protobuf RPC实现echo service一文看,细节本文不再描述。

google protobuf只负责消息的打包和解包,并不包含RPC的实现,但其包含了RPC的定义。假设有下面的RPC定义:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. service MyService {  
  2.         rpc Echo(EchoReqMsg) returns(EchoRespMsg)   
  3.     }  

那么要实现这个RPC需要最少做哪些事?总结起来需要完成以下几步:

客户端

RPC客户端需要实现google::protobuf::RpcChannel。主要实现RpcChannel::CallMethod接口。客户端调用任何一个RPC接口,最终都是调用到CallMethod。这个函数的典型实现就是将RPC调用参数序列化,然后投递给网络模块进行发送。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void CallMethod(const ::google::protobuf::MethodDescriptor* method,  
  2.                   ::google::protobuf::RpcController* controller,  
  3.                   const ::google::protobuf::Message* request,  
  4.                   ::google::protobuf::Message* response,  
  5.                   ::google::protobuf::Closure* done) {  
  6.         ...  
  7.         DataBufferOutputStream outputStream(...) // 取决于你使用的网络实现  
  8.         request->SerializeToZeroCopyStream(&outputStream);  
  9.         _connection->postData(outputStream.getData(), ...  
  10.         ...  
  11.     }  

服务端

服务端首先需要实现RPC接口,直接实现MyService中定义的接口:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. class MyServiceImpl : public MyService {  
  2.         virtual void Echo(::google::protobuf::RpcController* controller,  
  3.             const EchoReqMsg* request,  
  4.             EchoRespMsg* response,  
  5.             ::google::protobuf::Closure* done) {  
  6.             ...  
  7.             done->Run();  
  8.         }  
  9.     }  

标示service&method

基于以上,可以看出服务端根本不知道客户端想要调用哪一个RPC接口。从服务器接收到网络消息,到调用到MyServiceImpl::Echo还有很大一段距离。

解决方法就是在网络消息中带上RPC接口标识。这个标识可以直接带上service name和method name,但这种实现导致网络消息太大。另一种实现是基于service name和method name生成一个哈希值,因为接口不会太多,所以较容易找到基本不冲突的字符串哈希算法。

无论哪种方法,服务器是肯定需要建立RPC接口标识到protobuf service对象的映射的。

这里提供第三种方法:基于option的方法。

protobuf中option机制类似于这样一种机制:service&method被视为一个对象,其有很多属性,属性包含内置的,以及用户扩展的。用户扩展的就是option。每一个属性有一个值。protobuf提供访问service&method这些属性的接口。

首先扩展service&method的属性,以下定义这些属性的key:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. extend google.protobuf.ServiceOptions {  
  2.       required uint32 global_service_id = 1000;   
  3.     }  
  4.     extend google.protobuf.MethodOptions {  
  5.       required uint32 local_method_id = 1000;  
  6.     }  

应用层定义service&method时可以指定以上key的值:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. service MyService  
  2.     {  
  3.         option (arpc.global_service_id) = 2302;   
  4.   
  5.         rpc Echo(EchoReqMsg) returns(EchoRespMsg)   
  6.         {  
  7.             option (arpc.local_method_id) = 1;  
  8.         }  
  9.         rpc Echo_2(EchoReqMsg) returns(EchoRespMsg)   
  10.         {  
  11.             option (arpc.local_method_id) = 2;  
  12.         }  
  13.         ...  
  14.     }  

以上相当于在整个应用中,每个service都被赋予了唯一的id,单个service中的method也有唯一的id。

然后可以通过protobuf取出以上属性值:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void CallMethod(const ::google::protobuf::MethodDescriptor* method,  
  2.                   ::google::protobuf::RpcController* controller,  
  3.                   const ::google::protobuf::Message* request,  
  4.                   ::google::protobuf::Message* response,  
  5.                   ::google::protobuf::Closure* done) {  
  6.         ...  
  7.         google::protobuf::ServiceDescriptor *service = method->service();  
  8.         uint32_t serviceId = (uint32_t)(service->options().GetExtension(global_service_id));  
  9.         uint32_t methodId = (uint32_t)(method->options().GetExtension(local_method_id));  
  10.         ...  
  11.     }  

考虑到serviceId methodId的范围,可以直接打包到一个32位整数里:

[plain] view plaincopy在CODE上查看代码片派生到我的代码片
  1. uint32_t ret = (serviceId << 16) | methodId;  

然后就可以把这个值作为网络消息头的一部分发送。

当然服务器端是需要建立这个标识值到service的映射的:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. bool MyRPCServer::registerService(google::protobuf::Service *rpcService) {  
  2.         const google::protobuf::ServiceDescriptor = rpcService->GetDescriptor();  
  3.         int methodCnt = pSerDes->method_count();  
  4.   
  5.         for (int i = 0; i < methodCnt; i++) {  
  6.             google::protobuf::MethodDescriptor *pMethodDes = pSerDes->method(i);  
  7.             uint32_t rpcCode = PacketCodeBuilder()(pMethodDes); // 计算出映射值  
  8.             _rpcCallMap[rpcCode] = make_pair(rpcService, pMethodDes); // 建立映射  
  9.         }  
  10.         return true;  
  11.     }  

服务端收到RPC调用后,取出这个标识值,然后再从_rpcCallMap中取出对应的service和method,最后进行调用:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. google::protobuf::Message* response = _pService->GetResponsePrototype(_pMethodDes).New();  
  2.     // 用于回应的closure  
  3.     RPCServerClosure *pClosure = new (nothrow) RPCServerClosure(   
  4.             _channelId, _pConnection, _pReqMsg, pResMsg, _messageCodec, _version);  
  5.     RPCController *pController = pClosure->GetRpcController();  
  6.     ...  
  7.     // protobuf 生成的CallMethod,会自动调用到Echo接口  
  8.     _pService->CallMethod(_pMethodDes, pController, _pReqMsg, pResMsg, pClosure);  

参考

  • 使用google protobuf RPC实现echo service
  • protobuf extensions
  • protobuf service
  • protobuf options
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 被新浪屏蔽了怎么办 微博qq忘记密码怎么办 三星盖乐世忘记密码怎么办 三盖乐世4忘记密码怎么办 密码锁钥匙丢了怎么办 商标是tm标怎么办 环保合格证掉了怎么办 车子合格证丢了怎么办 大牙附近肿了怎么办 办好营业执照后怎么办商标 商标十年到期了怎么办 公司注册了商标怎么办 2018年属兔不适合结婚怎么办 交通事故对方没钱赔怎么办 滴滴快车出车祸怎么办 交警法院扣车怎么办 肇事逃逸没钱赔怎么办 撞死人无力赔偿怎么办 宝贝咳嗽很厉害怎么办 开车有人别车怎么办 判刑罚金交不起怎么办 罚金太多交不起怎么办 判决书上的罚金怎么办 刑事罚款不交怎么办 缎面的鞋脏了怎么办 夏天脸上长粉刺怎么办 鬃狮不吃东西怎么办 买二手车的车牌怎么办 榴莲打开了肉没熟怎么办 gmat的prep做完怎么办 电脑没ip地址怎么办 电脑ip地址缺失怎么办 cos还原不了人物怎么办 执行局抓人十五天不放人怎么办 笔记本电脑键盘按键错乱怎么办 靠墙倒立上不去怎么办 医院多收钱了怎么办 学生总是转笔怎么办 吃错东西呕吐怎么办 手腕筋扭伤了怎么办 右膝盖内侧疼痛怎么办