文章目录
asio
库基于操作系统提供的异步机制,采用前摄器设计模式(
Proactor
)实现了可移植的异步(或者同步)IO操作,而且并不要求使用多线程和锁定,有效地避免了多线程编程带来的诸多弊端(如条件竞争、死锁等)。
目前asio
主要关注与网络通信方面,使用大量的类和函数封装了socket API
,提供了一个现代c++风格的网络编程接口,支持TCP、ICMP、UDP
等网络通信协议。但asio
的异步操作并不局限于网络编程,它还支持串口读写、定时器、SSL
等功能,而且asio
是一个很好的富有弹性的框架,可以扩展到其他有异步操作的需要的领域。
使用asio
不需要编译,但它依赖于其他一些Boost库组件,最基本的是boost.system
和 boost.datetime
库,用来提供系统错误和时间支持。其他可选库有regex
、thread
和serialization
,如果支持SSL
,还要额外安装OpenSSL
。
接下来对asio
的介绍将以基本功能为主,即不使用system
和datetime
以外的Boost组件。asio
位于名字空间boost::asio
,因此需要包含的头文件形式如下:
#ifdef _MSC_VER
#define _WIN32_WINNT 0x0501
#endif
#define BOOST_REGEX_NO_LIB
#define BOOST_DATE_TIME_SOURCE
#define BOOST_SYSTEM_NO_LIB
#include <boost/asio.hpp>
using namespace boost::asio;
1 概述
asio
库基于前摄器模式(Proactor
) 封装了操作系统的select
、poll/epoll
、kqueue
、overlapped I/O
等机制,实现了异步IO模型。它的核心类是io_service
,相当于前摄器模式中的Proactor
角色,asio
的任何操作都需要有io_service
的参与。
在同步模式下,程序发起一个IO操作,向io_service
提交请求,io_service
把操作转交操作系统,同步地等待。当IO操作完成时,操作系统通知io_service
,然后io_service
再把结果发回给程序,完成整个同步流程。这个处理流程与多线程的join()
等待方式很相似。
在异步模式下,程序除了要发起的IO操作,还要定义一个用于回调的完成处理函数。io_service
同样把IO操作转交给操作系统执行,但它不同步等待,而是立即返回。调用io_service
的run()
成员函数可以等待异步操作完成,当异步操作完成时io_service
从操作系统获取执行结果,调用完成处理函数。
asio
不直接使用操作系统提供的线程,而且定义了一个自己的线程概念:strand
,它保证在多线程的环境下代码可以正确地执行,而无需使用互斥量。io_service::strand::wrap()
函数可以包装一个函数在strand
中执行。
IO操作会经常使用到缓冲区,asio
库专门用两个类 mutable_buffer
和 const_buffer
来封装这个概念,它们可以被安全地应用在异步的读写操作中,使用自由函数buffer()
能够包装常用的c++容器类型,如数组、array、vector、string等,用read()
、write()
函数来读取缓冲区。
asio
库使用system
库的error_code
和 system_error
来表示程序运行的错误。基本上所有的函数都有两种重载形式,一种形式是有一个error_code
的输出参数,调用后必须检查这个参数验证是否发生了错误;另一种形式没有error_code
参数,如果发生了错误会抛出system_error
异常,调用代码必须使用try-catch
块来捕获错误。在实际开发过程中,这两种形式都可以使用,各有利弊。
2 定时器
定时器是asio
库里最简单的一个IO模型示范,提供等候时间终止的功能,通过它我们可以快速熟悉asio
的基本使用方法。
定时器功能的主要类是deadline_timer,它的类摘要如下:
class deadline_timer
{
public:
explicit deadline_timer(io_service& io_service);
deadline_timer(io_service& io_service, const time_type& expiry_time);
void wait();
template<typename WaitHandler>
void async_wait(WaitHandler handler);
std::size_t cancel();
time_type expires_at();
std::size_t expires_at(const time_type& expiry_time);
duration_type expires_from_now();
std::size_t expires_from_now(const duration_type& expiry_time);
};
定时器deadline_timer
有两种形式的构造函数,都要求有一个 io_service
对象,用于提交IO请求,第二个参数是定时器的终止时间,可以是posix_time
的绝对时间点或者是自当前时间开始的一个时间长度。
一旦定时器对象创建,它就会立即开始计时,可以使用成员函数wait()
来同步等待定时器终止,或者使用async_wait()
异步等待,当定时器终止时会调用handler
函数。
如果创建定时器不指定终止时间,那么定时器不会工作,可以用成员函数expires_at()
和expires_from_now()
分别设置定时器终止的绝对时间和相对时间,然后再调用wait()
和async_wait()
等待。这两个函数也可以用于获得定时器的终止时间,只需要使用它们的无参重载形式。
定时器还有一个cancel()
函数,它的功能是通知所有异步操作取消,转而等待定时器终止。
3 定时器用法
3.1 同步定时器
下面的代码示范了deadline_timer
的用法:
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::asio; //打开asio名字空间
int main()
{
io_service ios; //所有asio程序必须要有一个io_service对象
deadline_timer t(ios, //定时器,io_service作为构造函数的参数
posix_time::seconds(2)); //两秒钟后定时器终止
cout << t.expires_at() << endl; //查看终止的绝对时间
t.wait(); //调用 wait()同步等待
cout << "hello asio" << endl; //输出一条消息
}
定时器同步等待两秒钟,当等待结束后输出一条信息,然后程序结束。
可以把它与thread
库的sleep()
函数对比研究,两者虽然都是等待,但是内部机制完全不同:thread
库的sleep()
使用了互斥量和条件变量,在线程中等待,而asio
则是调用了操作系统的异步机制,如select
、epoll
等完成的。
同步定时器的用法很简单,但它演示了asio
程序的基本结构和流程:
- 一个
asio
程序首先要定义一个io_service
对象,它是前摄器模式中最重要的proactor
角色 - 声明一个
IO
操作(在这里是定时器),并把它挂接在io_service
上, - 然后就可以执行后续的同步或异步操作。
3.2 异步定时器
接下来研究异步定时器,代码大致与同步定时器相等,增加了回调函数,并使用io_service.run()
和定时器的async_wait()
方法。
我们要定义回调函数,asio
库要求回调函数只能有一个参数,而且这个参数必须是const asio::error_code &
类型:
void print(system::error_code& /*e*/)
{
cout << "hello asio" << endl;
}
随后的异步定时器代码也同样很简单:
int main()
{
io_service ios; //io_service 对象
deadline_timer t(ios, posix_time::seconds(2)); //定时器
t.async_wait(print); //异步等待,传入回调函数,立即返回
cout << "it show before t expired." << endl;
ios.run(); //很重要!异步IO必须!
}
异步定时器的代码虽然变化不多,但实现的功能却有本质的差别。
代码的前两行与同步定时器相同,这是所有asio
程序基本的部分。重要的是异步等待async_wait()
,它通知io_service
异步地执行IO操作,并且注册了回调函数,用于在IO
操作完成时由事件多路分离器分派返回值(error_code)调用。
必须调用io_service
的run()
成员函数,它启动前摄器的事件处理循环,阻塞等待所有的操作完成并分派事件。如果不调用run()
那么虽然操作被异步执行了,但没有一个等待它完成的机制,回调函数将得不到执行机会。
当定时器时间到终止时 io_service