Protocol Buffer仅仅是提供了一套序列化和反序列化结构数据的机制,自己不具备RPC功能,可是能够基于其实现一套RPC框架。html
protocol buffer的Services类型是专门用来给RPC实现定义服务用的。git
定义示例以下:github
service SearchService { rpc Search (SearchRequest) returns (SearchResponse); }
Search是方法名,SearchRequest是参数,SearchResponse是返回类型,SearchRequest、SearchResponse分别都是预先定义的Message类型。这个Service通过编译后会生成一个SearchService类和其对应的stub实现SearchService_Stub。SearchService_Stub把调用都转给RpcChannel处理,RpcChannel是一个接口类,RPC系统中通常本身重载RpcChannel,例如你能够在重载类中把调用请求序列化后经过网络传输到服务端。而后客户端就能够像下面的代码同样进行RPC调用了:服务器
using google::protobuf; protobuf::RpcChannel* channel; protobuf::RpcController* controller; SearchService* service; SearchRequest request; SearchResponse response; void DoSearch() { // You provide classes MyRpcChannel and MyRpcController, which implement // the abstract interfaces protobuf::RpcChannel and protobuf::RpcController. channel = new MyRpcChannel("somehost.example.com:1234"); controller = new MyRpcController; // The protocol compiler generates the SearchService class based on the // definition given above. service = new SearchService::Stub(channel); // Set up the request. request.set_query("protocol buffers"); // Execute the RPC. service->Search(controller, request, response, protobuf::NewCallback(&Done)); } void Done() { delete service; delete channel; delete controller; }
服务端这边要实现Service接口,就是负责具体RPC函数的实现。而且在一网络接口上监听请求,处理请求,反序列化收到的网络数据后转调到这个函数的实现,以后把返回值序列化发回客户端做为调用结果。像下面的代码:网络
using google::protobuf; class ExampleSearchService : public SearchService { public: void Search(protobuf::RpcController* controller, const SearchRequest* request, SearchResponse* response, protobuf::Closure* done) { if (request->query() == "google") { response->add_result()->set_url("http://www.google.com"); } else if (request->query() == "protocol buffers") { response->add_result()->set_url("http://protobuf.googlecode.com"); } done->Run(); } }; int main() { // You provide class MyRpcServer. It does not have to implement any // particular interface; this is just an example. MyRpcServer server; protobuf::Service* service = new ExampleSearchService; server.ExportOnPort(1234, service); server.Run(); delete service; return 0; }
代码在这(https://github.com/persistentsnail/easy_pb_rpc)框架
package RPC; option cc_generic_services = true; message RpcRequestData { required uint32 service_id = 1; // 对应Service required uint32 method_id = 2; // 对应Service中的函数 required uint32 call_id = 3; // 对应本次调用(可能同一函数短期有屡次请求调用) required bytes content = 4; // 对应已经序列化了的函数参数Message } message RpcResponseData { required uint32 call_id = 1; // 对应请求的call_id required bytes content = 2; // 对应已经序列化了的返回值Message }
RPC请求是RpcRequestData Message,返回是RpcResponseData Message。service_id定义在一个配置文件services.cfg中,一个service_id对应一个服务名字,由服务端客户端共享,在程序启动时初始化一个一一映射的map。(这样的实现不太好,后面会提到)。在网络上传递的数据格式比较简单:异步
| Length of Encoding Binary Data (unsigned int) | RpcRequestData or RpcResponseData |。ide
支持RPC同步异步调用,例如:函数
void Foo(::google ::protobuf:: RpcController* controller , const ::FooRequest * request, :: FooResponse* response , :: google::protobuf ::Closure* done);
以回调参数Closure为准,若为NULL则是同步调用,反之异步回调之。内部实现上建立了一个底层工做线程,重载的RpcChannel实现把每次调用结构化一个一个msg放到msg queue中,工做线程从msg queue中取msg处理,具体来讲就是把msg序列化经过网络接口把请求传出去。逻辑上一个RpcChannel实例表明一个网络链接,因此能够重复使用一个RpcChannel对象。下面是同步调用一个EchoService的Foo方法的客户端代码示例:ui
RpcClient client ; RpcChannel channel(&client , "127.0.0.1:18669"); EchoService::Stub echo_clt(& channel); FooRequest request ; request.set_text ("test1"); request.set_times (1); FooResponse response ; RpcController controller ; echo_clt.Foo (&controller, & request, &response , NULL);
RpcClient管理全部链接会话,管理消息队列,工做线程,只须要一个实例对象,RpcChannel 使用RpcClient完成链接和转调。
首先注册服务,就是建立Service的实现类对象,放到容器里面。而后在一个网络端口上监听链接,解析网络数据包,根据不一样请求在服务容器里面找合适的service调用相应method。实现的比较简单,一个单线程服务器,同时只能处理一个请求。一个提供EchoService服务的server代码看起来是这样:
EchoServiceImpl *impl = new EchoServiceImpl(); RpcServer rpc_server ; rpc_server.RegisterService (impl); rpc_server.Start ();
服务端客户端网络数据处理使用的都是libevent。
1. 以前用到了service_id,要在双端同时维护一份service id和name互相对应的配置文件,不利于部署和更新。protocol buffer能够经过DescriptorPool自省出本身有哪些服务和方法的,能够参见http://www.cnblogs.com/Solstice/archive/2011/04/03/2004458.html。因此在定义协议的时候能够直接用service name而不是id,而那份配置文件天然也不须要。客户端用服务名字作一个RPC请求,服务端经过名字判断是否本身存在这个服务。相应的method_id也能够考虑用method name。可是用id也是有好处,id是数值类型使用的Base 128 Varints变长编码比字符串表示的name生成的数据包更小,另外数值作的哈希应该比DescriptorPool经过名字查找服务类更快。
2.应该充分使用protocol buffer错误处理方式,那就是使用RpcController 来作错误跟踪。
3.协议字段类型多使用optional,由于required字段是必须有数据的,相反optional却不必定须要,若是没有就是一个默认值。optional类型一般用来升级协议,好比一个Message添加了一个新的optinal字段,之前使用老的Message格式的代码序列出来的Message仍然可以被使用新的Message格式的代码正确解析,由于optional字段不存在,他会使用默认值;相似的,使用新的Message格式的代码序列出来的Message也可以被使用老的Message格式的代码正确解析,由于他会忽略不认识的字段,并且他不丢掉这个字段,也就是这个Message还能被继续正确的传输。