要用好它,就必须先了解它,而且不能停止于表面,必须深入到内部。而了解一件事物,先要了解它的框架,再了解它的细节。了解了框架,我们就有了提纲挈领的认识。
关于 boost asio 框架结构,在其文档中,用了这样一张图来描述:
简单解释一下:
这里由使用者(Initiator)启动一个异步操作(Asynchronous Operation),在启动异步的同时它要负责创建一个异步回调对象(Completion Handler),然后该异步操作被交给了异步操作执行者(Asynchronous Operation Processor),由它负责执行异步操作,并在完成后将一个完成事件插入完成事件队列(Completion Event Queue);另一方面,前摄器(Proactor,这个词很难准确翻译,也有翻译为主动器,可能借义于proactive)驱动异步事件分派器(Asynchronous Event Demultiplexer)从完成事件队列中获取事件,这是一个阻塞的过程,一旦获取到完成事件,前摄器从事件上找出与该事件关联的回调对象,并执行回调。
这是一个标准的前摄器模式,这个模式是在 ACE 网络库中使用的概念。关于该模式的研究很多,搜索一下 ACE Proactor 就可以找到很多资料。上面的描述也比较容易理解,唯一比较难搞懂的是异步事件分派器(Asynchronous Event Demultiplexer),好像它的存在并不起多大作用,其实它的作用大着呢,特别是在多线程中,它要保证异步完成事件的及时分派,提高多线程并发度,以及降低线程切换开销。在 windows 完成端口的文档中有这方面的机制介绍。
总得来说,这是一个概念性的模型,仅用这个模型来描述 boost asio,根本体现不了 boost asio 的优点。即使从使用者的角度,仅掌握这样的模型也是不够,boost asio 还有很多值得学习借鉴的地方。
我们需要结合这个模型来深入 boost asio 的实现框架。
boost asio 是如何实现前摄器模式的呢?我们使用 boost asio 第一步都需要创建一个 boost::asio::io_service,我们就从 io_service 开始开启我们的探秘之旅。
io_service 类的定义很简单,总共就三个成员变量:
其实简单反而意味着强大,因为这表明 boost asio 已经把功能结构划分的很清晰了。
三个成员变量中的 init_ 与结构没有太大关系,windows 平台的 winsock_init 里面调用了 WSAStartup,linux 平台的 signal_init 里面屏蔽了 SIG_PIPE 信号。这两个东东几乎是在我们刚开始接触网络编程就遇到的知识点。
其次我们再来看看 impl_ 成员,从名字上看应该是 io_service 的实现类,确实 io_service 的很多接口是直接转发给了 impl_ 成员,比如 run、poll、stop、reset、post、dispatch
但是 impl_type 是什么呢?它的定义如下:
原来是根据不同的平台支持特性,选择了不同的实现,要把这么多种实现融合起来,没有一个很好的架构是很难做到的。
我们再来看看 service_registry_,对这个成员变量的使用体现在 use_service、add_service、has_service 三个函数中:
看起来是个集合容器,里面的元素是服务(Service),服务有编号(id)。从 service_registry 的实现可以进一步了解到下面细节:
继续分析每个服务,还可以看到服务有下列特性:
最后,总结出所有的服务大概分为三类:
第一类服务是底层系统服务,是对操作系统平台提供功能的封装,有:
中间四个都是 reactor,不能想象,所有的 reactor 应该有相同的对外服务接口。这里的 task_io_service 和 reactive_socket_service 是对 reactor 的再封装,所以上层的服务不会直接依赖 reactor,而是依赖 task_io_service 和 reactive_socket_service。
第二类服务是上层接口服务,面向具体的功能对象(Object),他们会针对运行平台选择依赖对应的底层服务
前四个 socket 相关的服务会在不同的平台,选择依赖 win_iocp_socket_service 和 reactive_socket_service<xxx_reactor>,deadline_timer_service (具体实现在
detail::deadline_timer_service中)会选择 win_iocp_io_service 和 task_io_service<xxx_reactor>,ip::resolver_service 没得选择,只有依赖 detail::resolver_service。
第三类服务是一些特殊功能的服务,比如 detail::strand_service 等,他们对整体框架没有影响。
虽然 boost asio 中提供了这么多服务,但是上层应用并不会直接使用这些服务,服务通过句柄对外暴露其功能,而句柄被功能对象(Object)封装,然后提供给上层应用使用。
这里的功能对象(Object),就是我们在第二类服务后面的“()”里面给出的类,每个对象都包含着一个对相应服务的C++引用,以及服务对外暴露的句柄。
至此,我们了解了 boost asio 中通过一系列的服务封装了操作系统的底层功能,并且通过动态组装的方式把这些服务管理起来。可以看出,boost asio 使用了一种相当简单的方式,就解决了平台的多样性,甚至于模式的多样性;同时服务的动态加载和集中管理,为功能扩展提供了方便途径;另外对象中只包含句柄,提高了安全性。
再回过头来看看,我们心中还有个疑问,那就是 boost asio 是怎么实现前摄器(Proactor)模式的呢?其实前摄器(Proactor)的各个角色都是通过服务来表达的。
先看 windows 平台
Asynchronous Operation Processor 的功能是由 win_iocp_socket_service、resolver_service 完成,Proactor 功能由 win_iocp_io_service 完成,win_iocp_io_service 也包含 Asynchronous Event Demultiplexer 和 Completion Event Queue 的功能,不过其实是对 windows IOCP 的系统功能的封装。
再看看 linux 平台
Asynchronous Operation Processor 的功能是由 reactive_socket_service、resolver_service 完成,Proactor、Asynchronous Event Demultiplexer 和 Completion Event Queue功能都是由 task_io_service 完成。
最后,io_service 其实代表了Proator 这一端,socket、timer、resolver 等等代表了 Initiator 这一端。这就是上层使用者角度看到的景象。