这篇文章将讲述如何使用google的protobuf库实现一个RPC service,就实现一个最简单的service吧:echo. 文章对应的代码均可以在eventrpc中找到,写下这篇文章时的svn revision是138.linux
[cpp] view plain copy算法
package echo; message EchoRequest { required string message = 1; }; message EchoResponse { required string response = 1; }; service EchoService { rpc Echo(EchoRequest) returns (EchoResponse); };
解释一下这个proto文件中作的事情,它定义了一个package: echo, 这个package中有service:EchoService,而这个service下只有一个服务:Echo, 它的请求由EchoRequest结构体定义,回复由EchoResponse定义. package至关因而C++中namespace的概念,有些package中可能会提供相同名字的service,为了解决命名冲突,就引入了package这个概念.安全
所生成的C++文件,都会在namespace echo中,就是前面提到的package概念.对于service EchoService而言,会对应的生成两个类:EchoService类和EchoService_Stub类:服务器
[cpp] view plain copy网络
class EchoService : public ::google::protobuf::Service { // .... EchoService_Stub(::google::protobuf::RpcChannel* channel); virtual void Echo(::google::protobuf::RpcController* controller, const ::echo::EchoRequest* request, ::echo::EchoResponse* response, ::google::protobuf::Closure* done); void CallMethod(const ::google::protobuf::MethodDescriptor* method, ::google::protobuf::RpcController* controller, const ::google::protobuf::Message* request, ::google::protobuf::Message* response, ::google::protobuf::Closure* done); }; class EchoService_Stub : public EchoService { //... void Echo(::google::protobuf::RpcController* controller, const ::echo::EchoRequest* request, ::echo::EchoResponse* response, ::google::protobuf::Closure* done); };
上面省略了一些细节,只把最关键的部分提取出来了. 这两部分如何使用,后面会继续讲解app
[cpp] view plain copy异步
Dispatcher dispatcher; RpcChannel *channel = new RpcChannel("127.0.0.1", 21118, &dispatcher); if (!channel->Connect()) { printf("connect to server failed, abort\n"); exit(-1); } echo::EchoService::Stub stub(channel); echo::EchoRequest request; echo::EchoResponse response; request.set_message("hello"); stub.Echo(NULL, &request, &response, gpb::NewCallback(::echo_done, &response, channel));
能够看到,stub类的构造函数须要一个::google::protobuf::RpcChannel指针,这个类须要咱们来实现,后面继续说.而后就是根据协议填充请求字段,注册回调函数,这以后就能够调用stub类提供的Echo函数发送请求了.svn
[cpp] view plain copy函数
void EchoService_Stub::Echo(::google::protobuf::RpcController* controller, const ::echo::EchoRequest* request, ::echo::EchoResponse* response, ::google::protobuf::Closure* done) { channel_->CallMethod(descriptor()->method(0), controller, request, response, done); }
能够看到,发送请求的背后,最后调用的实际上是RpcChannel的CallMethod函数.因此,要实现RpcChannel类,最关键的就是要实现这个函数,在这个函数中完成发送请求的事务.具体能够看rpc_channel.cpp中的作法,再也不阐述,由于这里面作的事情,和通常的网络客户端作的事情差很少.测试
如何识别service 前面提到过,每一个service的请求包和回复包都是protobuf中的message结构体,在这个例子中是EchoRequest和EchoResponse message.但是,它们仅仅是包体,也就是说,即便你发送了这些消息,在服务器端还须要一个包头来识别究竟是哪一个请求的包体. 因而在代码中,引入了一个类Meta,其中有两个关键的变量:包体长度和method id. 包体长度自没必要说,就是紧跟着包头的包体数据的长度. method id是用来标识哪个service的,若是不用id数字,也可使用字符串,每一个service,都有一个full name的概念,以这里的例子而言,Echo服务的full name是echo::EchoService::Echo(再次的,又是C++中namespace的概念来表示”全路径”以免命名冲突).可是,若是使用full name来区分,一来发送包头就会过大,而来查找service时是一个字符串比较操做的过程,耗时间. 因此引入了method id的概念,选择hash full name为一个id值,通常而言,一个服务器对外提供的service,撑死有几百个吧,而选用的id是整型数据,另外再选择足够好的hash算法,绝大多数状况下是不会出现冲突的. 以上就是Meta类作的事情,封装了包体和识别service的method id,一并做为包头和包体拼接发送给服务器端.
实现服务器端 接收到客户端的请求以后,首先要作一些安全性的检查,好比method id对应的service是否有注册. 其次就是真正的处理过程了:
[cpp] view plain copy
int RpcMethodManager::HandleService(string *message, Meta *meta, Callback *callback) { RpcMethod *rpc_method = rpc_methods_[meta->method_id()]; const gpb::MethodDescriptor *method = rpc_method->method_; gpb::Message *request = rpc_method->request_->New(); gpb::Message *response = rpc_method->response_->New(); request->ParseFromString(*message); HandleServiceEntry *entry = new HandleServiceEntry(method, request, response, message, meta, callback); gpb::Closure *done = gpb::NewCallback( &HandleServiceDone, entry); rpc_method->service_->CallMethod(method, NULL, request, response, done); return 0; }
上面注册了一个名为HandleServiceDone的回调函数,当service的Echo处理完毕以后,自动就会调用这个回调函数 来看 EchoService::CallMethod的定义
[cpp] view plain copy
void EchoService::CallMethod(const ::google::protobuf::MethodDescriptor* method, ::google::protobuf::RpcController* controller, const ::google::protobuf::Message* request, ::google::protobuf::Message* response, ::google::protobuf::Closure* done) { GOOGLE_DCHECK_EQ(method->service(), EchoService_descriptor_); switch(method->index()) { case 0: Echo(controller, ::google::protobuf::down_cast<CONST ::echo::EchoRequest*>(request), ::google::protobuf::down_cast< ::echo::EchoResponse*>(response), done); break; default: GOOGLE_LOG(FATAL) << "Bad method index; this should never happen."; break; } }
能够看到, 这个Echo服务是须要注册的服务器端首先实现的,以echo_server.cpp中的代码为例,它是这样作的:
[cpp] view plain copy
class EchoServiceImpl : public echo::EchoService { public: EchoServiceImpl() { }; virtual void Echo(::google::protobuf::RpcController* controller, const ::echo::EchoRequest* request, ::echo::EchoResponse* response, ::google::protobuf::Closure* done) { printf ("request: %s\n", request->message().c_str()); response->set_response(request->message()); if (done) { done->Run(); } } };
它作的事情就是把收到的请求打印出来,而后将请求消息做为回复消息传送回去.调用done->Run()函数,其实就是调用前面注册的回调函数HandleServiceDone函数,这时候表示服务器端已经准备好了给客户端响应的消息,后面就是网络传输层的事情了.
以上是使用google protobuf RPC实现一个service的全过程.protobuf官方并无给出这样一个demo的例子,因此我在eventrpc项目中试图封装protobuf来作RPC service. 可是,当前的实现还不够完善,存在如下的问题:
不过,就以上而言,若是想了解如何使用protobuf来实现RPC,已经足够说明原理了,能够对应着代码和官方文档看看每一个类的含义. 要编译成功,须要protobuf库和phread库.以前曾经使用libevent,可是不喜欢这个东东,因而就本身作了,可是目前仅支持epoll而已,因此还只能在linux上面编译.