12.2 Boost-Asio(笔记)


asio 库基于操作系统提供的异步机制,采用前摄器设计模式( Proactor)实现了可移植的异步(或者同步)IO操作,而且并不要求使用多线程和锁定,有效地避免了多线程编程带来的诸多弊端(如条件竞争、死锁等)。

目前asio主要关注与网络通信方面,使用大量的类和函数封装了socket API,提供了一个现代c++风格的网络编程接口,支持TCP、ICMP、UDP等网络通信协议。但asio的异步操作并不局限于网络编程,它还支持串口读写、定时器、SSL等功能,而且asio是一个很好的富有弹性的框架,可以扩展到其他有异步操作的需要的领域。

使用asio不需要编译,但它依赖于其他一些Boost库组件,最基本的是boost.systemboost.datetime 库,用来提供系统错误和时间支持。其他可选库有regexthreadserialization,如果支持SSL,还要额外安装OpenSSL

接下来对asio的介绍将以基本功能为主,即不使用systemdatetime以外的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) 封装了操作系统的selectpoll/epollkqueueoverlapped 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_servicerun()成员函数可以等待异步操作完成,当异步操作完成时io_service从操作系统获取执行结果,调用完成处理函数。

asio不直接使用操作系统提供的线程,而且定义了一个自己的线程概念:strand,它保证在多线程的环境下代码可以正确地执行,而无需使用互斥量。io_service::strand::wrap()函数可以包装一个函数在strand中执行。

IO操作会经常使用到缓冲区,asio库专门用两个类 mutable_bufferconst_buffer 来封装这个概念,它们可以被安全地应用在异步的读写操作中,使用自由函数buffer()能够包装常用的c++容器类型,如数组、array、vector、string等,用read()write()函数来读取缓冲区。

asio库使用system库的error_codesystem_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则是调用了操作系统的异步机制,如selectepoll等完成的。

同步定时器的用法很简单,但它演示了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_servicerun()成员函数,它启动前摄器的事件处理循环,阻塞等待所有的操作完成并分派事件。如果不调用run()那么虽然操作被异步执行了,但没有一个等待它完成的机制,回调函数将得不到执行机会。

当定时器时间到终止时 io_service

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值