在 muduo 中实现 protobuf 编解码器与动静分发器
当前位置:以往代写 > C/C++ 教程 >在 muduo 中实现 protobuf 编解码器与动静分发器
2019-06-13

在 muduo 中实现 protobuf 编解码器与动静分发器

在 muduo 中实现 protobuf 编解码器与动静分发器

副标题#e#

本文是《一种自动反射动静范例的 Google Protobuf 网络传输方案》的延续,先容如何将前文先容 的打包方案与 muduo::net::Buffer 团结,实现了 protobuf codec 和 dispatcher。

Muduo 的 下载地点: http://muduo.googlecode.com/files/muduo-0.1.9-alpha.tar.gz ,SHA1 dc0bb5f7becdfc0277fb35f6dfaafee8209213bc ,本文的完整代码可在线阅读 http://code.google.com/p/muduo/source/browse/trunk/examples/protobuf/codec/ 。

思量 到不是每小我私家都安装了 Google Protobuf,muduo 中的 protobuf 相关示例默认是不 build 的,假如 你的呆板上安装了 protobuf 2.3.0 或 2.4.0a,那么可以用 ./build.sh protobuf_all 来构建 protobuf 相关的 examples。

在先容 codec 和 dispatcher 之前,先讲讲前文的一个未决问题 。

为什么 Protobuf 的默认序列化名目没有包括动静的长度与范例?

Protobuf 是颠末 深思熟虑的动静打包方案,它的默认序列化名目没有包括动静的长度与范例,自然有其原理。哪些环境 下不需要在 protobuf 序列化获得的字节约中包括动静的长度和(或)范例?我能想到的谜底有:

假如把动静写入文件,一个文件存一个动静,那么序列化功效中不需要包括长度和范例,因为从文 件名和文件长度中可以得知动静的范例与长度。

假如把动静写入文件,一个文件存多个动静,那么序列化功效中不需要包括范例,因为文件名就代 表了动静的范例。

假如把动静存入数据库(可能 NoSQL),以 VARBINARY 字段生存,那么序列化功效中不需要包括长 度和范例,因为从字段名和字段长度中可以得知动静的范例与长度。

假如把动静以 UDP 方法产生给对方,并且对方一个 UDP port 只吸收一种动静范例,那么序列化结 果中不需要包括长度和范例,因为从 port 和 UDP packet 长度中可以得知动静的范例与长度。

假如把动静以 TCP 短毗连方法发给对方,并且对方一个 TCP port 只吸收一种动静范例,那么序列 化功效中不需要包括长度和范例,因为从 port 和 TCP 字节约长度中可以得知动静的范例与长度。

假如把动静以 TCP 长毗连方法发给对方,可是对方一个 TCP port 只吸收一种动静范例,那么序列 化功效中不需要包括范例,因为 port 代表了动静的范例。

假如回收 RPC 方法通信,那么只需要汇报对方 method name,对方自然能揣度出 Request 和 Response 的动静范例,这些可以由 protoc 生成的 RPC stubs 自动搞定。

对付最后一点,例如说 sudoku.proto 界说为:

service SudokuService {
  rpc Solve (SudokuRequest) returns (SudokuResponse);
}

那么 RPC method Sudoku.Solve 对应的请求和响应别离是 SudokuRequest 和 SudokuResponse。在发送 RPC 请求的时候,不需要包括 SudokuRequest 的范例,只需要发送 method name Sudoku.Solve,对方自知道应该凭据 SudokuRequest 来理会(parse)请求。这个例子来自我的半 制品项目 evproto,见 http://blog.csdn.net/Solstice/archive/2010/04/17/5497699.aspx 。

对付上述这些环境,假如 protobuf 无条件地把长度和范例放到序列化的字节串中,只会挥霍 网络带宽和存储。可见 protobuf 默认不发送长度和范例是正确的抉择。Protobuf 为动静名目标设计 树立了规范,哪些该本身搞定,哪些留给外部系统去办理,这些都思量得很清楚。

只有在利用 TCP 长毗连,且在一个毗连上通报不止一种动静的环境下(例如同时发 Heartbeat 和 Request/Response),才需要我前文提到的那种打包方案。(为什么要在一个毗连上同时发 Heartbeat 和业务动静?请见陈硕《漫衍式系统的工程化开拓要领》 p.51 心跳协议的设计。)这时候我们需要一 个分发器 dispatcher,把差异范例的动静分给各个动静处理惩罚函数,这正是本文的主题之一。


#p#副标题#e#

以 下均只思量 TCP 长毗连这一应用场景。

先谈谈编解码器。

什么是编解码器 codec?

Codec 是 encoder 和 decoder 的缩写,这是一个到软硬件都在利用的术语,这里我借指“把 网络数据和业务动静之间相互转换”的代码。

在最简朴的网络编程中,没有动静 message 只有 字节约数据,这时候是用不到 codec 的。好比我们前面讲过的 echo server,它只需要把收到的数据 原封不动地发送归去,它不必体贴动静的界线(也没有“动静”的观念),收几多就发几多,这种环境 下它爽性直接利用 muduo::net::Buffer,取到数据再交给 TcpConnection 发送归去,见下图。

在 muduo 中实现 protobuf 编解码器与消息分发器

non-trivial 的网络处事措施凡是会以动静为单元来通信,每条动静有明晰的长度与边界。措施每 次收到一个完整的动静的时候才开始处理惩罚,发送的时候也是把一个完整的动静交给网络库。好比我们前 面讲过的 asio chat 处事,它的一条谈天记录就是一条动静,我们设计了一个简朴的动静名目,即在 谈天记录前面加上 4 字节的 length header,LengthHeaderCodec 代码及讲解见《Muduo 网络编程示 例之二:Boost.Asio 的谈天处事器》一文。

#p#分页标题#e#

codec 的根基成果之一是做 TCP 分包:确定每条 动静的长度,为动静分别边界。在 non-blocking 网络编程中,codec 险些是必不行少的。假如只收到 了半条动静,那么不会触动员静回调,数据会逗留在 Buffer 里(数据已经读到 Buffer 中了),期待 收到一个完整的动静再通知处理惩罚函数。既然这个任务太常见,我们爽性做一个 utility class,制止服 务端和客户端措施都要本身处理惩罚分包,这就有了 LengthHeaderCodec。这个 codec 的利用有点奇怪, 不需要担任,它也没有基类,只要把它当成普通 data member 来用,把 TcpConnection 的数据喂给它 ,然后向它注册 onXXXMessage() 回调,代码见 asio chat 示例。muduo 里的 codec 都是这样的气势气魄 ,通过 boost::function 粘合到一起。

codec 是一层间接性,它位于 TcpConnection 和 ChatServer 之间,拦截处理惩罚收到的数据,在收到完整的动静之后再挪用 CharServer 对应的处理惩罚函数 ,留意 CharServer::onStringMessage() 的参数是 std::string,不再是 muduo::net::Buffer,也就 是说 LengthHeaderCodec 把 Buffer 解码成了 string。别的,在发送动静的时候,ChatServer 通过 LengthHeaderCodec::send() 来发送 string,LengthHeaderCodec 认真把它编码成 Buffer。这正是“ 编解码器”名字的由来。

在 muduo 中实现 protobuf 编解码器与消息分发器

#p#副标题#e#

Protobuf codec 与此很是雷同,只不外动静范例从 std::string 酿成了 protobuf::Message。对 于只吸收处理惩罚 Query 动静的 QueryServer 来说,用 ProtobufCodec 很是利便,收到 protobuf::Message 之后 down cast 成 Query 来用就行。假如要吸收处理惩罚不止一种动静, ProtobufCodec 恐怕还不能单独完成事情,请继承阅读下文。

在 muduo 中实现 protobuf 编解码器与消息分发器

实现 ProtobufCodec

Protobuf 的打包方案我已经在《一种自动反射动静范例的 Google Protobuf 网络传输方案》中讲过,并以 string 为载体演示了 encode 和 decode 操纵。在 muduo 里 ,我们有专门的 Buffer class,编码更轻松。

编码算法很直截了当,凭据前订亲义的动静名目 一路打包下来,最后更新一下首部的长度即可。

解码算法有几个要点:

protobuf::Message 是 new 出来的工具,它的生命期如何打点?muduo 回收 shared_ptr 来自动管 理工具生命期,这与其他处所的做法是一致的。

堕落如那里理惩罚?例如说长度超出范畴、check sum 不正确、message type name 不能识别、message parse 堕落等等。ProtobufCodec 界说了 ErrorCallback,用户代码可以注册这个回调。假如不注册, 默认的处理惩罚是断开毗连,让客户重连重试。codec 的单位测试里模仿了各类堕落环境。

如那里理惩罚一次收到半条动静、一条动静、一条半动静、两条动静等等环境?这是每个 non-blocking 网络措施中的 codec 都要面临的问题。

ProtobufCodec 在实际利用中有明明的不敷:它只认真把 muduo::net::Buffer 转换为详细范例的 protobuf::Message,应用措施拿到 Message 之后尚有再按照其详细范例做一次分发。我们可以思量做 一个简朴通用的分发器 dispatcher,以简化客户代码。

另外,今朝 ProtobufCodec 的实现非 常低级,它没有充实操作 ZeroCopyInputStream 和 ZeroCopyOutputStream,而是把收到的数据作为 byte array 交给 protobuf Message 去理会,这给机能优化留下了空间。protobuf Message 不要求数 据持续(像 vector 那样),只要求数据分段持续(像 deque 那样),这给 buffer 打点带来机能上的好 处(制止从头分派内存,淘汰内存碎片),虽然也使得代码变巨大。muduo::net::Buffer 很是简朴, 它内部是 vector,我今朝不想让 protobuf 影响 muduo 自己的设计,究竟 muduo 是个通用的网络库 ,不是为实现 protobuf RPC 而特制的。

动静分发器 dispatcher 有什么用?

前面提到 ,在利用 TCP 长毗连,且在一个毗连上通报不止一种 protobuf 动静的环境下,客户代码需要对收到 的动静按范例做分发。例如说,收到 Logon 动静就交给 QueryServer::onLogon() 去处理惩罚,收到 Query 动静就交给 QueryServer::onQuery() 去处理惩罚。这个动静分配机制可以做得稍微有点通用性,让 所有 muduo+protobuf 措施收益,并且不增加巨大性。

#p#分页标题#e#

换句话说,又是一层间接性, ProtobufCodec 拦截了 TcpConnection 的数据,把它转换为 Message,ProtobufDispatcher 拦截了 ProtobufCodec 的 callback,按动静详细范例把它分配给多个 callbacks。

在 muduo 中实现 protobuf 编解码器与消息分发器

#p#副标题#e#

ProtobufCodec 与 ProtobufDispatcher 的综合运用

我写了两个示例代码,client 和 server,把 ProtobufCodec 和 ProtobufDispatcher 串联起来利用。server 响应 Query 动静,产生 回 Answer 动静,假如收到未知动静范例,则断开毗连。client 可以选择发送 Query 或 Empty 动静 ,由呼吁行节制。这样可以测试 unknown message callback。

为节减篇幅,这里就不列出代码 了,请移步阅读

http://code.google.com/p/muduo/source/browse/trunk/examples/protobuf/codec/client.c c

http://code.google.com/p/muduo/source/browse/trunk/examples/protobuf/codec/server.c c

在结构函数中,通过注册回调函数把四方 (TcpConnection、codec、dispatcher、 QueryServer) 团结起来。

ProtobufDispatcher 的两种实现

要完成动静分发,那么就是 对动静做 type-switch,这好像是一个 bad smell,可是 protobuf Message 的 Descriptor 没有留下 定制点(好比袒露一个 boost::any 成员),我们只好硬来了。

先界说

typedef boost::function ProtobufMessageCallback;

留意,本节呈现的不是 muduo dispatcher 真实 的代码,仅为示意,突出重点,便于绘图。

ProtobufDispatcherLite 的布局很是简朴,它有一 个 map 成员,客户代码可以以 Descriptor* 为 key 注册回调(recall: 每个详细动静范例都有一个 全局的 Descriptor 工具,其地点是稳定的,可以用来当 key)。在收到 protobuf Message 之后,在 map 中找到对应的 ProtobufMessageCallback,然后挪用之。假如找不到,就挪用 defaultCallback。

在 muduo 中实现 protobuf 编解码器与消息分发器

虽然,它的设计也有小小的缺陷,那就是 ProtobufMessageCallback 限制了客户代码只能接管基类 Message,客户代码需要本身做向下转型,好比:

在 muduo 中实现 protobuf 编解码器与消息分发器

假如我但愿 QueryServer 这么设计:不想每个动静处理惩罚函数本身做 down casting,而是交给 dispatcher 去处理惩罚,客户代码拿到的就已经是想要的详细范例。如下:

在 muduo 中实现 protobuf 编解码器与消息分发器

查察本栏目

#p#副标题#e#

那么该该 如何实现 ProtobufDispatcher 呢?它如何与多个未知的动静范例相助?做 down cast 需要知道方针 范例,莫非我们要用一长串模板范例参数吗?

有一个步伐,把多态与模板团结,操作 templated derived class 来提供范例上的机动性。设计如下。

在 muduo 中实现 protobuf 编解码器与消息分发器

ProtobufDispatcher 有一个模板成员函数,可以接管注册任意动静范例 T 的回调,然后它建设一 个模板化的派生类 CallbackT,这样动静的类新信息就生存在了 CallbackT 中,做 down casting 就 简朴了。

例如说,我们有两个详细动静范例 Query 和 Answer。

在 muduo 中实现 protobuf 编解码器与消息分发器

然后我们 这样注册回调:

dispatcher_.registerMessageCallback(
    boost::bind(&QueryServer::onQuery, this, _1, _2, _3));
dispatcher_.registerMessageCallback(
    boost::bind(&QueryServer::onAnswer, this, _1, _2, _3));

这样会具现化 (instantiation) 出两个 CallbackT 实体,如下:

在 muduo 中实现 protobuf 编解码器与消息分发器

以上设计参考了 shared_ptr 的 deleter,Scott Meyers 也谈到过。

ProtobufCodec 和 ProtobufDispatcher 有何意义?

ProtobufCodec 和 ProtobufDispatcher 把每个直吸收发 protobuf Message 的网络措施城市用到的成果提炼出来做成了公用的 utility,这样今后新写 protobuf 网络措施就不必为打包分包和动静分发费神了。它俩以库的形式存在,是两个可以拿来就当 data member 用的 class,它们没有基类,也没有用到虚函数可能此外什么面向工具特征,不侵入 muduo::net 可能你的代码。假如不这么做,那未来每个 protobuf 网络措施都要本身从头实现雷同的 成果,徒增承担。

下一篇文章讲《漫衍式措施的自动回归测试》会先容操作 protobuf 的跨语 言特性,回收 Java 为 C++ 处事措施编写 test harness。

    关键字:

在线提交作业