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. 给2021年的我立几个FLAG

    看多了大牛的年终总结,我也懒得写了,反正写出来也没人看. 其实上面都是借口,我只是完全没有写年终总结的习惯. 为啥呢?因为这些年过的平平无奇,并没有什么特别出彩的事情. 如果有,嗯,2020年,我结婚 ...

  2. sqlite嵌入式数据库简介及特性

    p.p1 { margin: 0; font: 12px "Helvetica Neue"; color: rgba(69, 69, 69, 1) } p.p2 { margin: ...

  3. linux系统搭建ftp服务器及创建用户使用

    linux 系统下搭建ftp服务器 ftp是什么 FTP是 File Transfer Protocol 文件传输协议的英文名称,用于在Internet上控制文件的双向传输. 同时它也是一个应用程序. ...

  4. 单细胞分析实录(8): 展示marker基因的4种图形(一)

    今天的内容讲讲单细胞文章中经常出现的展示细胞marker的图:tsne/umap图.热图.堆叠小提琴图.气泡图,每个图我都会用两种方法绘制. 使用的数据来自文献:Single-cell transcr ...

  5. 关于ajax 异步文件上传 node 文件后台接口

    <body> <img src="" alt="" id="img"> <input type="f ...

  6. WPF TreeView Indent 减少节点的缩进

    www.swack.cn - 原文链接:WPF TreeView Indent 减少节点的缩进 问题 最近一个需求,需要在界面中实现Windows资源管理器TreeView的界面.但是我发现,我做出的 ...

  7. MyBatis中id回填的两种方式

    在一种场景下需要刚刚插入数据的ID,如果数据少可以先看数据库,记下ID,但数据很多,假设一万个用户并发,每个用户都插入自己的ID,就很难记下来. 下面给定一个场景: 1 User user = new ...

  8. 【C++】《C++ Primer 》第十四章

    第十四章 重载运算与类型转换 一.基本概念 重载运算符是具有特殊名字的函数:由关键字operator和其后要定义的运算符号共同组成.也包含返回类型.参数列表以及函数体. 当一个重载的运算符是成员函数时 ...

  9. java中如何踢人下线?封禁某个账号后使其会话立即掉线!

    需求场景 封禁账号是一个比较常见的业务需求,尤其是在论坛.社区类型的项目中,当出现了违规用户时我们需要将其账号立即封禁. 常规的设计思路是:在设计用户表时增加一个状态字段,例如:status,其值为1 ...

  10. VSCode运行时弹出powershell

    问题 安装好了vscode并且装上code runner插件后,运行代码时总是弹出powershell,而不是在vscode底部终端 显示运行结果. 解决方法 打开系统cmd ,在窗口顶部条右击打开属 ...