概况
-
asio基于操作系统提供的异步机制,采用proactor设计模式实现了可移植的异步(或者同步)IO操作,而且并不要求使用多线程和锁,有效避免了多线程编程带来的副作用(比如条件竞争、死锁等)
- asio基于Proactor模式封装了操作系统的
select、poll、epoll、kqueue
等机制,实现了异步/同步IO模型。 它的核心类是io_server
,相当于Proactor模式中的Proactor
。asio的任何操作都需要io_server
的参与。
- 在同步模式下,程序发起一个IO操作,向
io_server
提交请求,io_server
把操作转交给操作系统,同步的等待。当IO操作完成之后,操作系统通知io_server
,然后io_server
在把结果发回给程序,完成整个同步过程。类型多线程的join()
- 在异步模式下,程序除了要发起的IO操作外,还要定义一个用于回调的完成处理函数(complete handler)。
io_server
同样把IO操作转交给操作系统执行,但它不同步等待,而是立即返回,调用io_server
的run
成员函数可以等待异步操作完成,等异步操作完成时io_server
会从操作系统获取结果,再调用handler处理后续逻辑。
- asio基于Proactor模式封装了操作系统的
-
目前asio主要关注在网络通信方面,使用了大量的类和函数封装了SocketAPI,提供了一个现代C++分格的网络编程接口,支持TCP/UDP/ICMP等网络通信协议。但asio的异步操作并不局限于网络编程,它还支持UNIX信号、定时器、串口读写、SSL等功能,而且asio是一个很好的富有弹性的框架,可以扩展到其他有异步操作需要的领域。
-
asio不需要编译,但是它依赖其他一些boost库组件:
- 最基本的是boost.system库,用来提供系统错误支持。
- 与thread库不同的是它默认使用标准库< chrono >提供的时间功能,如果要强制使用boost.chrono可以定义宏BOOST_ASIO_DISABLE_STD_CHRONO
- 其他可选库还有coroutine、regex、thread和serialization,如果支持SSL,还需要额外安装OpenSSL
-
asio位于名字空间boost::asio,需要包含的头文件如下:
#define BOOST_ASIO_DISABLE_STD_CHRONE # 使用boost.chrono库的时间功能
#include <boost/asio.hpp>
using namespace boost::asio;
handler
- handler是符合某种函数签名的回调函数。handler必须是可拷贝的,io_server会存储handler的拷贝,当某种异步事件发生时io_service就会调用事件对应的handler
- handler并不一定必须是函数或者函数指针。函数对象、function对象、bind/lambda表达式等可调用对象都可以用于io_service调用。但是要注意,由于operator()是异步发生的,时机并不确定,必须保证它们引用的外部变量可用,否则会发生未定义行为。
在asio库里handler主要有如下三种:
- 只有一个error_code参数,标志某个异步事件完成了,是最基本的handler
- error_code和signal_number两个参数,标识发生了一个UNIX信号事件
- error_code和bytes_transferred两个参数,标识某个读写操作完成了,可读的数据字节数是bytes_transferred,通常用于socket回调
这三种handler对应的形式是:
可以使用bind把任意函数适配为asio要求的handler形式,asio库在子名字空间boost::asio::placeholders里定义了几个新的占位符,比bind自己的_1,_2等更好:
- error:表示error_code值
- signal_number:表示UNIX信号值
- bytes_transferred:表示可读写的字节数
io_service
- io_service类代表了系统里的异步处理机制(比如epoll),必须在asio库里的其他对象之前初始化,其他对象则向io_service提交异步操作的handler
- 最常用的成员函数是run(),它启动事件循环,阻塞等待所有注册到io_service的事件完成
ps
新版 ASIO 必须以 asio::io_context 替换 asio::io_service
io_context -> io_service
io_context.post() -> io_context.get_executor().post()
io_context.dispatch() -> io_context.get_executor().dispatch()
io_context::strand -> strand< io_context::executor_type>
strand
asio库基于操作系统的异步IO模型,不直接使用系统线程,而是定义了一个自己的线程概念:strand,它序列化异步操作,保证异步代码在多线程的环境中可以正确的执行,无需使用互斥量
strand常用的成员函数是wrap(),它可以包装一个函数,返回一个相同签名的函数对象,保证线程安全地在strand中执行。
我们可以把strand理解为一组handler的锁,加入了线程保护,这一组里的handler不会存在线程并发访问的问题。
- 在一个线程里面使用io_service是没有竞争的,本身就是线程安全,不需要strand。
- 但是如果多个线程对一个io_service对象执行run(),那么同一个handler就有可能存在线程竞争,需要使用strand保护。
work
- 当io_service里注册的所有事件完成时它就会退出事件循环,有时候这不是我们想要的,我们需要让io_service仍然继续运行,处理将来可能发生的异步事件,这时就需要让io_service始终“有事情做”
- io_service的内部类work就可以做到这样:
- 它在构造函数里启动了一个可用的“work”,在析构函数里停止“work”,这样在work的生命周期里io_server就永远不会因为其他异步事件完成而结束循环
- 如果想要work停止,有两种方法:
- 显示调用它的析构函数
- 使用智能指针持有它,停止工作时reset智能指针
mutable_buffer和const_buffer
mutable_buffer和const_buffer:
- 是什么:IO操作会经常使用到数据缓冲区,相当于一片指定的内存区域,asio库专门用两个类mutable_buffer和const_buffer来表示这个概念。
- 实现原理:保存了一个void *的内存地址和数据长度
mutable_buffer_1和const_buffer_1:
- 是什么:是一个适配器,专门为mutable_buffer和const_buffer提供了容器操作
buffer()
- buffer()是一个工厂函数,它可以将常用的C++容器类型,比如array、vector、string等包装为mutable_buffer_1或者const_buffer_1。
- 参数: 常用的C++容器类型,比如array、vector、string等。
- 返回值:mutable_buffer_1或者const_buffer_1对象
- 怎么操作buffer呢?有如下函数:
错误处理
asio库使用system库的error_code和system_error来表示程序运行的错误。基本上所有的异步操作函数都有两种形式:
- 一:有一个error_code &的输出参数,调用后可以检查这个参数验证是否出错,可以忽略错误
- 二:没有error_code &输出参数,如果发生错误会抛出system_error异常,调用时必须使用try+catch来捕获错误,无法忽略
比如io_service的几个成员函数的error_code形式是:
跟踪日志
异步执行的代码是非线性的,很难调试,所以asio提供了handler跟踪机制。
boost::asio::io_service::work
问题
- 当有任务时,run函数会一直阻塞;但是当没有任务了,run函数会返回,所有异步操作将会终止
- 客户端程序中,如果我想连接断开后重连,但是由于连接断开了,run会返回,当再次重连的时候,由于run返回了,即使连接成功了,也不会调用async_connect绑定的回调函数
怎么解决
- 方法一: 在再次重连的时候,要重新调用run函数,在调用的前一定要调用io_service::reset。以便io_service::run重用。
boost::asio::io_service io_service_;
io_service_.reset();
io_service_.run();
- 方法二:用boost::asio::io_service::work
boost::asio::io_service io_service_;
boost::asio::io_service::work work(io_service_);
io_service_.run();
thread库和asio库
相似
- 都可以用于并发编程
不同
- thread使用的是进程内部的现场机制,很少需要操作系统内核干预
- asio使用的是异步事件处理机制,与操作系统内核密切相关。由于把异步操作交给了操作系统处理,所以性能更高