Asio源码分析(1):概述
Asio是一个跨平台的C++网络库,目前它的一部分已经成为了C++ Networking TS(C++ Technical specifications),并有望加入到C++23。从Asio将并入标准库这一点来看,它应该是一个不错的网络库,有一定的学习价值。
这是Asio源码分析系类文章的第一篇,这个系列要解决的问题主要有:
- Asio的源码用到了哪些C++技巧,做了哪些优化?
- 在Linux下,Asio是如何通过epoll来实现Proactor模式的?
- 在Linux下,Asio是如何实现并发的?
- Asio的异步模型是如何与C++20协程结合的?
该系列使用的Asio版本为1.18.2
。通过对Asio源码的阅读,可以感受到作者C++编码水平的高超,整个库提供了很多定制点(Customization points)。
Proactor和Reactor设计模式
Asio的异步支持基于Proactor设计模式。首先,我们简单对比一下Reactor模式和Asio的Proactor模式有什么不同。
当前普遍存在的网络库很多都是Reactor设计模式,例如libevent
、muduo
。而Asio是Proactor模式。Asio的Proactor模式会导致编写的代码很奇怪,它会将程序的业务逻辑分散在各个回调函数之中,提高了编码的复杂度,相对不如Reactor模式清晰(其实Asio也支持Reactor-style的编码方式)。例如使用Asio实现一个tcp echo server。但Proactor可以很轻易地与协程相结合,让我们可以编写和goroutine一样简洁的代码。
从UNIX网络编程中定义的5中I/O模型来看,Proactor模式类似于异步I/O,而Reactor模式类似于I/O多路复用。
Reactor模式的实现方式基本为I/O多路复用,其中的select
、poll
或epoll
就是一个Reactor。
- 以读操作为例,Reactor模式的大致流程为:用户注册一个读回调函数,程序阻塞在Reactor上,每当有读操作可用时便调用该回调函数(一些网络库还会管理缓冲,在执行完读操作后才调用回调函数)。一般读回调函数不需要重复注册,只需注册一次即可。
- 而进行写操作时,一般会由网络库负责完整地将数据发出,用户只需要调用对应的函数然后直接返回即可。
Reactor模式在用户的角度来看,程序阻塞于Reactor,等待读操作可用,而写操作立即返回。
而Proactor模式与Reactor模式的流程有一些不同。
- 以读操作为例,Proactor模式有一个Initiator用于发起一次异步读操作,其中指定一个回调函数,然后立即返回。当读操作可用并执行完读操作之后,调用Initiator指定的回调函数。
- 写操作与读操作类似,只是将发起一次读操作换成发起一次异步写操作而已。
Proactor模式在用户的角度来看,读写操作都立即返回。
Asio的Proactor模式的典型代码大致如下(忽略错误处理和tcp消息边界相关问题) :
#include <iostream>
#include "asio.hpp"
char read_buf[4096];