ASIO 腾空出世 (那些年我们追过的网络库.PartII)

Posted on September 18, 2015

这是上篇 那些年我们追过的网络库(PartI) 的后续,

ASIO 腾空出世

在地球最大的岛上,另一位少年开始拜读 ACE 的大作。那时候,没有 libuv 没有 libev 更没有 libevent . 有的只是 ACE. 然而这个南方小国的少年没有跟风陷入 ACE 崇拜,他以敏锐的目光察觉到了 ACE 的弊病。 ACE 哪里做的不好?又哪里是值得借鉴的?

少年在给 c++ 委员会写的一篇上书中说,Proactor 模型乃最优模型。而 Proactor 模型,乃 ACE 提出的 6 个模型之一。根据 IT 界赢者通吃律,一个优秀的网络库,只需要支持 Proactor 模型即可。 支持其他次优模型都是徒劳的。ACE 试图全盘通吃,犯了大忌。

少年在一次开发者大会的演讲上,再次透露,网络库不宜做成框架,而是要像系统的API那样,作为一个乐高积木。ACE 做成了一个框架,同样不妥。

你说了那么多 ACE 不好,有本事你弄个好的啊? 批评者向来都是这么理直气壮。 正如 ACE 的作者实践了 “纸上得来终觉浅 绝知此事要躬行” 一样,这位勇敢的少年也拿出了 ASIO, “实践出真知”,他如是说。

那还是 SARS 病毒肆虐的年代,几乎没有人注意到,今后颠覆C++的网络世界的 ASIO 悄然出世了。而他的父亲,还只是悉尼的学子。ASIO 并没有显赫的家庭背景,然而英雄不问出处,它注定将有不平凡的一生。

俗语有云,三岁看老。在 asio 才三岁的时候,它父亲就将 asio 引荐给了 c++ 委员会的老人们。上一次他们这么做的时候,他们接纳了 STL。 ASIO 最终被内定,然后放入 Boost 锻炼, 经过 Boost 十余的锻炼,ASIO 终于在 2017 年进入了 c++ 标准。

Proactor ? Why Proactor

在给 c++ 老人会的引荐信里,asio 爸爸仔细阐述了asio的设计抉择回答了围绕 asio 的设计提出的很多问题。 为什么 Proactor 会是最佳模型?

  • 跨平台 许多操作系统都有异步API,即便是没有异步API的Linux, 通过 epoll 也能模拟 Proactor 模式。
  • 支持回调函数组合 将一系列异步操作进行组合,封装成对外的一个异步调用。这个只有Proactor能做到,Reactor 做不到。意味着如果asio使用Reactor模式,就对不起他“库” 之名。
  • 相比 Reactor 可以实现 Zero-copy
  • 和线程解耦 长时间执行的过程总是有系统异步完成,应用程序无需为此开启线程

Proactor 也并非全无缺点,缺点就是内存占用比 Reactor 大。Proactor 需要先分配内存而后处理IO, 而 Reactor 是先等待 IO 而后分配内存。相对的Proactor却获得了Zero-copy好处。因为内存已经分配好了,因此操作系统可以将接受到的网络数据直接从网络接口拷贝到应用程序内存,而无需经过内核中转。 Proactor 模式需要一个 loop ,这个 loop asio 将其封装为 io_service.他不仅是 asio的核心,更是一切基于asio设计的程序的核心。

宇宙级异步核心

io_service 脱胎于 IO 但不仅用于 IO. Christopher Kohlhoff 在给委员会的另一份编号 N3747 的信上上说它是 宇宙级异步模型 Universal Asynchronous Model。在宇宙级异步模型里,一个异步操作由三部分构成

  1. 发起 按照 asio 的编码规范,所有的发起操作都使用 async_ 前缀,使用 async_动词 的形式作为函数名。
  2. 执行 异步过程在发起的时候被executor执行(系统可以是支持 AIO 的内核,不支持 AIO 的系统则是 aiso 用户层模拟)
  3. 完成并回调 在发起 async_* 操作的时候,总是携带一个回调的闭包。asio使用闭包作为异步事件完成的处理回调,没而不是C式的回调函数。asio的宇宙异步模型里,回调总是在执行 io_service::run 的线程里执行。asio绝不会在内部线程里调用回调。

在回调里发起新的异步操作,一轮套一轮。整个程序就围绕着 io_service::run 运转起来了。 io_service 不仅仅能用于异步 IO ,还可以用来投递任意闭包。实现作为线程池的功能。这一通用型异步模型彻底击败微软 PPL 提案,致使微软转而研究协程。然而微软在协程上同样面临 asio 的绞杀。

闭包和协程

宇宙级 asio 使用闭包作为回调,而 C 库只能使用函数+void*, ACE 虽然使用的 C++语言,却不知道闭包为何物,使用的是 虚函数作为回调。需要大量的从 ACE 的对象继承。以闭包为回调,asio更是支持了一种叫“无栈协程”的强悍武器。 asio的无栈协程,仅仅通过库的形式,不论是在性能上,还是易用性上,还是简洁性上,甚至是B格上,都超过了微软易于修改语言而得的 await提案。

微软,乃至 ACE ,并不是不知道闭包,而是在c++里实现闭包的宇宙级executor —— 也就是 io_service,需要对模板技术的精通。 asio “把困难留给自己,把方便带给大家”,以地球人无法理解的方式硬是在 c++98 上实现了宇宙级异步核心。 当然,如果 c++11 早点出现,如果 c++17 早点出现,实现 asio 的宇宙模型会更加的简单 —— 其实这也是 c++ 的理念,增加语言特性,只是为了让语言用起来更简单。

buffers

有了闭包的支持,内存管理也变得轻轻松松起来。 ASIO 本身并不管理内存,所有的IO操作,只提交对用户管理的内存的引用,称 Buffers。asio::buffers 引用了用户提交的内存,保持整个 IO 期间,这块内存的有效性是用户的责任。然而这并不难! 因为回调是一个闭包。通过闭包持有内存,只要 asio 还未回调,闭包就在,闭包在,内存在。asio 在调用完回调后才删除相应的闭包。因此资源管理的责任可以丢给闭包,而闭包可以通过智能指针精确的控制内存。 不是 GC , 胜于 GC 千百倍!益于c++的 RAII机制,再无内存泄漏之忧!

进入 ASIO 的世界

对 C++ 网络库的历史也介绍到差不多了,接下来的章节里,带你深入理解asio , 让你同时获得开发效率和执行效率。 一些在本章里,可能对许多人来说都是初次见到的技术,将在本书剩余章节里详细介绍。

翻开下一页,进入 ASIO 的世界,领略 C++ 的博大精深,享受网络遨游的快感吧!

Comments