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. 私有Maven仓库安装和使用

    安装 这里选择安装的是windows平台,linux安装类似,下载可能要梯子 官网下载页 https://help.sonatype.com/repomanager2/download 下载链接 ht ...

  2. Git 分支模型与开发规范

    GitHub Flow & Git Flow 基于Git 的两种协作开发模式 01.分支模型 master:长期分支,一般用于管理对外发布版本,每个 commit 对一个 tag,也就是一个发 ...

  3. vue watch监听不到对象,探究 watch 原理

    最近使用vue watch时,在某些模块监听不到对象的改变,无法触发回调函数. 解决: 使用watch监听对象时,只能监听到该对象初始化时已存在的key值. 如下例监听user对象,在初始化时没有ag ...

  4. java中远程调用接口springboot

    package com.kakarote.crm.utils; import cn.hutool.core.util.ObjectUtil; import org.apache.http.client ...

  5. 【Mongodb】后台主键_id自增(Java版本)

    ObjectId的选择 创建MongoDB文档时,如果没有赋值ID,系统会自动帮你创建一个,通常会在客户端由驱动程序完成.得到的ObjectId类似于这种   ObjectId使用12字节的存储空间, ...

  6. [LeetCode]662. Maximum Width of Binary Tree判断树的宽度

    public int widthOfBinaryTree(TreeNode root) { /* 层序遍历+记录完全二叉树的坐标,左孩子2*i,右孩子2*i+1 而且要有两个变量,一个记录本层节点数, ...

  7. css 自定义悬浮窗写法

    HTML: css(sass): 复制代码 .info { } &:hover::after { content: attr(data-title); display: inline-bloc ...

  8. 主从同步遇到 Got fatal error 1236 from master when reading data from binary log: 'Could not find first log file name in binary log index file'时怎么解决

    首先遇到这个是因为binlog位置索引处的问题,不要reset slave: reset slave会将主从同步的文件以及位置恢复到初始状态,一开始没有数据还好,有数据的话,相当于重新开始同步,可能会 ...

  9. RocetMQ搭建攻略和问题解决之道

    最近有在尝试给项目加入消息中间件服务,首先想到了鼎鼎大名的RocketMQ.RocketMQ是一款高性能的.分布式消息中间件,由阿里开源.它提供了丰富的消息拉取方式,能够处理上亿级的海量数据,甚至在阿 ...

  10. ios iphone 崩溃字符记录

    如题,近日iphone被爆出有一串字符可引发系统错误 (بٍٍٍٍََُُُِّّّْرٍٍٍٍََُُِِّّّْآٍٍٍَُّ بٍٍٍٍََُُُِّّّْرٍٍٍٍََُُِِّّّْآٍٍٍ ...