Protocol Buffer仅仅是提供了一套序列化和反序列化结构数据的机制,本身不具有RPC功能,但是可以基于其实现一套RPC框架。

Services

protocol buffer的Services类型是专门用来给RPC实现定义服务用的。

定义示例如下:

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;

}

一个RPC实现

代码在这(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 |。

客户端

支持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方法的客户端代码示例:

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。

一些protocol buffer的细节

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还能被继续正确的传输。

一个基于protocol buffer的RPC实现的更多相关文章

  1. Hadoop基于Protocol Buffer的RPC实现代码分析-Server端

    http://yanbohappy.sinaapp.com/?p=110 最新版本的Hadoop代码中已经默认了Protocol buffer(以下简称PB,http://code.google.co ...

  2. Hadoop基于Protocol Buffer的RPC实现代码分析-Server端--转载

    原文地址:http://yanbohappy.sinaapp.com/?p=110 最新版本的Hadoop代码中已经默认了Protocol buffer(以下简称PB,http://code.goog ...

  3. Ggoogle Protocol Buffer的使用 (基于C++语言)

    首先说明的是Protocol Buffle是灵活高效的.它的一个很好的优点(很重要的,我认为)就是后向兼容性--当我们扩展了了.proto文件后,我们照样可以用它来读取之前生成的文件. 之前已经写了关 ...

  4. 一个基于protobuf的极简RPC

    前言 RPC采用客户机/服务器模式实现两个进程之间的相互通信,socket是RPC经常采用的通信手段之一.当然,除了socket,RPC还有其他的通信方法:http.管道...网络开源的RPC框架也比 ...

  5. 后端程序员之路 39、一个Protocol Buffer实例

    实际工作的Protocol Buffer使用经验 # 写proto文件- 协议版本 项目用的是protobuf2,所以要指定 syntax = "proto2";- 包名 pack ...

  6. Google Protocol Buffer 的使用和原理[转]

    本文转自: http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/ Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构 ...

  7. Corba、protocol buffer、SOA的区别 (转)

    From: http://www.zhihu.com/question/20279489 Google的protocol buffers?这个跟corba.soa没啥关系,不同层次的概念,没法比.pr ...

  8. 使用hessian+protocol buffer+easyUI综合案例--登陆

    首先先简单介绍下hessian ,protocol buffer, easyUI框架 hessian: Hessian是一个轻量级的remoting on http工具,采用的是Binary RPC协 ...

  9. Google Protocol Buffer 的使用和原理

    Google Protocol Buffer 的使用和原理 Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,很适合做数据存储或 RPC 数据交换格式.它 ...

随机推荐

  1. 记录第一次使用Vivado——以全加器为例子

    从altera转战xilinx,经典的FPGA到ZYNQ系列,第一站就是先熟悉编译软件Vivado.我就直接跳过软件安装部分了,如有疑问,可以在评论区提出来,我看到了就帮你解答. 首先是是打开界面 然 ...

  2. (java)五大常用算法

    算法一:分治法 基本概念 1.把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题--直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并. 2.分治策略是对于一个 ...

  3. [leetcode]24. Swap Nodes in Pairs交换链表的节点

    感觉这个题后台的运行程序有问题,一开始自己想的是反转链表那道题的方法,只是隔一个节点执行一次,但是没有通过,TLE了,但是很奇怪,并没有死循环,就是最后返回的时候超时. 最后的思路就是很简单的进行交换 ...

  4. 广告计价方式:CPM,CPC,CPA

    转自知乎链接:https://www.zhihu.com/question/20416888/answer/15076251 1.CPM 收费最科学的办法是按照有多少人看到你的广告来收费.按访问人次收 ...

  5. Python & Matplotlib: Monte Carlos Method

    Hey! 这里是Lindy:) Hope you guys are doing well! 今天想记录的概念叫做 蒙特·卡罗 方法,是今年在cs课上老师做的扩展延伸.其实我在初次接触这个概念时觉得很新 ...

  6. HashSet/HashMap 存取值的过程

    HashSet与HashMap的关系: (1)HashSet底层用的是HashMap来实现的 (2)这个HashMap的key就是放进HashSet中的对象,value就是一个Object类型的对象 ...

  7. Spark Streaming 与Filnk对比分析

    转:https://mp.weixin.qq.com/s/jllAegJMYh_by95FhHt0jA

  8. 消息队列 ---常用的 MQ 中间件

    目前市面上比较常用的 MQ(Message Queue,消息队列)中间件有 RabbitMQ.Kafka.RocketMQ,如果是轻量级的消息队列可以使用 Redis 提供的消息队列,其中 Redis ...

  9. Command3

    压缩和解压 gzip gunzip .gz 指定文件必须带后缀 gzip file filename.gz zip unzip .zip unzip filename.zip directory zi ...

  10. Second_week_mofangzhen

    第二周 奇数阶魔方阵 一.上节回顾 1.数组的基本操作 数组:若干个相同类型变量的集合. 声明:数据类型 数组名称[]; (在栈内存分配空间,存储的是数组的引用地址.数组首元素在堆内存 中的地址) 初 ...