boost:asio学习

概况

  • 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_serverrun成员函数可以等待异步操作完成,等异步操作完成时io_server会从操作系统获取结果,再调用handler处理后续逻辑。
  • 目前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 
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对应的形式是:

boost:asio学习_第1张图片
可以使用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的事件完成
    boost:asio学习_第2张图片

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,它序列化异步操作,保证异步代码在多线程的环境中可以正确的执行,无需使用互斥量

boost:asio学习_第3张图片
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智能指针

boost:asio学习_第4张图片

mutable_buffer和const_buffer

mutable_buffer和const_buffer:

  • 是什么:IO操作会经常使用到数据缓冲区,相当于一片指定的内存区域,asio库专门用两个类mutable_buffer和const_buffer来表示这个概念。
  • 实现原理:保存了一个void *的内存地址和数据长度

boost:asio学习_第5张图片

mutable_buffer_1和const_buffer_1:

  • 是什么:是一个适配器,专门为mutable_buffer和const_buffer提供了容器操作

boost:asio学习_第6张图片
buffer()

  • buffer()是一个工厂函数,它可以将常用的C++容器类型,比如array、vector、string等包装为mutable_buffer_1或者const_buffer_1。
    • 参数: 常用的C++容器类型,比如array、vector、string等。
    • 返回值:mutable_buffer_1或者const_buffer_1对象
      在这里插入图片描述
  • 怎么操作buffer呢?有如下函数:
    boost:asio学习_第7张图片

错误处理

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使用的是异步事件处理机制,与操作系统内核密切相关。由于把异步操作交给了操作系统处理,所以性能更高

你可能感兴趣的:(C++,学习,网络,服务器)