博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
google的protobuf库
阅读量:7236 次
发布时间:2019-06-29

本文共 7036 字,大约阅读时间需要 23 分钟。

hot3.png

这篇文章将讲述如何使用google的protobuf库实现一个RPC service,就实现一个最简单的service吧:echo. 文章对应的代码都可以在eventrpc中找到,写下这篇文章时的svn revision是138.

  1. 定义协议 首先需要为这个service定义proto文件, 如下:

[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这个概念.

  1. 对应的C++文件 使用protobuf自带的编译proto文件编译器,可以生成对应的pb.h和pb.cc文件.具体细节可以参考protobuf关于这部分的参考文档.

所生成的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);  };

上面省略了一些细节,只把最关键的部分提取出来了. 这两部分如何使用,后面会继续讲解

  1. 实现客户端 首先来看如何实现客户端. 客户端都通过上面提到的对应service的stub类来发送请求,以sample/echo_client.cpp中的代码来解释:

[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函数发送请求了.

  1. 实现RpcChannel类 现在可以讲解RpcChannel类和stub类的关系了,看看在调用stub::Echo函数,也就是发送请求时发生了什么事情:

[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中的做法,不再阐述,因为这里面做的事情,和一般的网络客户端做的事情差不多.

  1. 如何识别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,一并作为包头和包体拼接发送给服务器端.

  2. 实现服务器端 接收到客户端的请求之后,首先要做一些安全性的检查,比如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
(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. 但是,当前的实现还不够完善,存在以下的问题:

  1. 效率不高
  2. 没有实现客户端可以选择异步或者同步方式来响应服务器端的消息
  3. 安全性检查不够完善,目前仅适用method id来检查
  4. 没有把dispatcher抽出来独立到一个线程中,只有这样才能实现2)
  5. 没有为每个函数写测试用例. .... N) 其他还没有想到的....等着您给建议

不过,就以上而言,如果想了解如何使用protobuf来实现RPC,已经足够说明原理了,可以对应着代码和官方文档看看每个类的含义. 要编译成功,需要protobuf库和phread库.之前曾经使用libevent,但是不喜欢这个东东,于是就自己做了,但是目前仅支持epoll而已,所以还只能在linux上面编译.

转载于:https://my.oschina.net/u/1426828/blog/1825133

你可能感兴趣的文章
ERROR 1217 (23000): Cannot delete or update a parent row: a foreign key constraint fails
查看>>
spark集群配置
查看>>
OpenNMS安装与试用
查看>>
安装Win2012 - 体验Windows 2012 Server 的虚拟化技术【1】
查看>>
QListWiget等样式表
查看>>
计算100以内所有奇数的和以及所有偶数的和
查看>>
搭建本地私有Kolla docker registry
查看>>
Docker 定制ssh、java等基础服务镜像
查看>>
jvm参数设置和分析
查看>>
伟大的程序员是怎样炼成的?
查看>>
vCenter Operations for View快速上手指南
查看>>
tomcat搭建
查看>>
$_SERVER获取URL地址函数的区别
查看>>
LABEL MATRIX里用计数器自动生成流水号和自定义条码格式
查看>>
java 实现一套流程管理、流转的思路(伪工作流)
查看>>
动态修改小程序标题栏文字
查看>>
【转】家庭wifi覆盖指导
查看>>
mac IDE输入光标变成块状 改为竖线
查看>>
linux 协议栈tcp的rst报文中,seq的选取问题
查看>>
一个铜钱的故事
查看>>