C++11 学习笔记(8) 使用bind改进 rtmpd iohandler 与 protocol 的绑定关系的思路

rtmpd ( 即 crtmpserver )的 是C++版本的流媒体服务器,
其实现方式与 Java 版本的 red5类似. 源码中有大量的指针互相
注册, 以便每个类都能找到对应的功能类, 做通知功能或者传输数据.
而指针的声明周期又需要作者自己清楚地了解(没有用到 shared_ptr).
也许在 C++11推出后, 可以方便地使用 std::bind 与 std::function
来重新定义类之间的关系, 通过互相的注册函数(包括通知函数).

在 rtmpd 的 docs 文档中有一个 architecture.txt 描述了 rtmpd 的代码结构:

文档中 T 即 rtmpd 中的 iohandler 类, 而 iohander 与 protocol 层的类
多是用指针互相引用和绑定. 为了将 iohandler 与实际业务的 protocol 类耦合
性降低, 作者其实添加了做了一层 protocol (tcpprotocol, udpprotocol)
eg:
比如基于 tcp 的 rtmp协议, 在 rtmpd 中实现结构是
iohander(tcp fd) <---> tcpprotocol <---> rtmpprotocol tcpprotocol
即绑定了 iohandler, 又与上一层 rtmpprotocol 绑定, 这要求 tcpprotocol 在
编译时要了解 rtmp 业务层相关的实现类(源码里各种 HAS_XXX 宏 判断). 已知在
tcpprotocol OnEvent 又有 处理buffer 的读和写事件的操作. 尤其是读事件后
把 buffer 转交给 rtmpprotocol 类解析数据.

好了, 先暂停说 rtmpd, 切换到我的工作场景:
我在工作项目上开发上传文件服务器时, 为了迅速搭建一个 demo,
采用了 boost asio 库和 boost 的 streambuf 类组合,
借鉴了 rtmpd iohandler 与 iohandlermanager 的实现方式,
以及 boost 官方文档提供的 tcp accept 处理的例子
开发发现通信层的框架的 connection 类(或者是其他需要与
上一层业务层交互数据的通信类),
要求有一个中间件,将 connection(或者叫 iohandler)与
实际业务的 protocol放一起. 这个中间件作用就是 connection来消息了,
将消息交给 protocol类处理, 或者 protocol类写消息时交给对应的
connection类处理.

此时产生一个疑问: 有没有必要实现一个中间件(或者是以类的方式实现)?….
啊哈, C++11 标准推出后, 有 std::bind 与 std::function 可以语法上实现
闭包( 后来组长说其实 boost 里的bind 与 function 的用法和我下面介绍的方式
早就有人这么干了 –b, 还是要多看书~~ )

改进方式就是, iohandler 与 rtmpprotocol 直接绑定:
伪代码:
iohandler 定义 成员变量:
std::function<bool(const u_int8_t *buf,
std::size_t bufLen)> NotifyReadBufferEvent_;
成员函数:
// 通知 iohandler 已经解析的 buffer 长度(或者已经读取了多少buffer长度)
bool SignalHasReadBufferLen(std::size_t hasReadLen);

rtmprotocol 的基类 baseprotocol 定义成员函数

这里要求一个强制的规则是: iohandler 读事件产生后, buffer 填充可读数据,
此时回调 NotifyReadBufferEvent_, protocol类接收buffer, 将buffer
中数据解析, 已经解析的长度需要上一层业务开发者调用 SignalHasReadBufferLen,
将已经解析的长度通知到 iohandler, 这样下一次异步读事件再回调时, buffer的
位置是已经更新的,而不会又读取到的是上一次的数据. buffer 会在
SignalHasReadBufferLen 内部调整. buffer 类似告诉 buffer, 已经消耗了多少.
boost 的 streambuf 有一个consume函数, 即类似调整 buffer 的功能.

如何绑定 iohandler 和 baseprotocol:
iohandler 没必要知道上一层业务的类名字. 同时, acceptor 类需要在一个全局的
globalconfig里将 新连接的 fd 与生成的对应 iohandler 传入,
在这里交给全局的注册函数绑定.
(
不讨论globalconfig实现, 目前想法还在雏形中, 自己的开源项目ceabiscuit
打算尝试实现, ceabiscuit 名字源自于 seabiscuit 一个励志故事,
也是一部相关的电影名字 :)
)

绑定代码:

用函数做为成员变量代替将具体实现类的指针绑定二者之间关系,
将底层和业务层严格分离.
缺点:
1. 需要注册多个回调,(除了通知 ReadEvent, 还有部分未说明)
2. 严格按照规则玩(上一层开发者要遵守调用规则)

优点:
1. 底层异步io框架与业务层框架耦合性降低.
2. 异步io框架可以单独拿出来, 适配到其他的业务服务中, 降低开发成本

与 封装 epoll 或者 libev 库的区别: 只是更进一层封装, 底层的
buffer, tcp, udp 只需通过初始化配置.

发表评论

电子邮件地址不会被公开。 必填项已用*标注