Boost::asio概览

版权声明:所有的博客都是个人笔记,交流可以留言。未经允许,谢绝转载。。。 https://blog.csdn.net/qq_35976351/article/details/90084029

Boost::asio核心的概念和功能

asio的最核心的功能是用于异步的IO通信, 比如通过文件 网络或者控制台等. asio提供了一系列的工具来处理这种长时间的IO操作, 而且执行这些操作不需要依赖线程和锁的模型.

笔记参考自官方文档: https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview.html

基础的结构

参考自: https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/core/basics.html

同步IO模型

程序执行同步IO的模型:

在boost.asio中,上述分别的对应接口如下:

io_service表示程序与操作系统IO服务的链接,对应的boost接口

boost::asio::io_context io_context;

io_object相当于一个IO对象, 比如socket的,这在boost中,有很多例子,比如

boost::asio::ip::tcp::socket socket(io_context);

在类unix系统中, 操作失败都有对应的错误码,boost也是如此, 上述的同步链接中

boost::system::error_code ec;
socket.connect(server_endpoint, ec);

失败的话, ec会保存对应的错误码, 该函数会阻塞, 直到建立连接成功或者失败, 这也是同步IO的特点.

异步IO模型

异步IO模型图

异步IO最明显的一个特点是, 进行IO时,不需要阻塞等待返回, 而是可以先执行其它的任务, 然后去读取IO的内容.

程序可能的步骤是:
初始化IO对象

socket.async_connect(server_endpoint, your_completion_handler);

其中your_completion_handler的原型是

void your_completion_handler(const boost::system::error_code& ec);

如果有参数, 可以是bind操作, 进行参数绑定操作.

操作系统执行IO的时候, 程序可以进行其它的操作, OS会把有关的结果放到就绪队列中, 等待io_context读取. 读取操作在your_completion_handler执行, 需要调用io_context::run()来启动.

Proactor设计模式

Linux中, 有个很经典的Reactor模型, 这是指有一个主线程负责监听IO事件, 然后把产生IO的文件描述符和对应的操作传递给一个线程中, 由线程池进行驱动. 因此, Reactor模式需要一个主线程和若干个线程.

而Proactor模式一般只需要一个线程即可. 先给出Proactor模式的示例图:

  • Asynchronous Operation: 异步操作, 比如对socket的读写
  • Asynchronous Operation Processor: 执行异步操作, 同时把读写就绪事件放到就绪队列中
  • Completion Event Queue: 存储读写就绪事件
  • Completion Handler: 用户自定义的处理IO的函数, 一般需要借助bind操作
  • Asynchronous Event Demultiplexer: 在Completion Event Queue上阻塞等待, 把就绪事件返回给它的调用者.
  • Proactor: 调用Asynchronous Event Demultiplexer来使得就绪事件出队, 之后把就绪事件分配给相应的处理句柄, 这由io_service来完成.
  • Initiator: 启动整个异步操作流程.

strand, 使用无锁的线程

官方文档: https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/core/strands.html

一个strand可以理解为: 一系列严格有序的操作, 通过strand操作, 可以让这一系列的操作在多线程中不显式地加锁执行.

strand分为显式和隐式两种, 对应三种情况:

  • 调用io_context::run()函数, 这是隐式执行, 所有的操作序列都在一个线程中执行, 可定不用加锁
  • 一组connect操作, 比如HTTP连接, 这是隐式操作
  • 通过strand<>或者io_context:strand显式说明, 所用的事件处理的函数对象需要通过boost::asio::bind_executor()绑定, 否则需要strand对象进行分发操作.

很多时候, 我们面对的是异步操作的组合, 这些组合构成的操作通过一个strand. 那么, 我们需要保证跨线程时, 调用者和操作者处于不同线程时, 要有足够的安全性, 通过下面的函数获取executor, 比如:

boost::asio::associated_executor_t<Handler> a = boost::asio::get_associated_executor(h);

Buffer和流的操作

因为IO操作的数据需要经过内存, 那么必然有buffer的概念, 具体可以查找手册进行处理. 流式的数据, 也需要借助有关的流操作实现, 具体可以参考手册.

Reator模式的操作

借助第三方库等完成操作.
https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/core/reactor.html

Line-Based数据模式操作

相当于是数据流的结束符, 因为很多是面向流的数据.
https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/core/line_based.html

调试追踪异步操作

主要是为了异步调试方便, 具体参考手册:
https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/core/handler_tracking.html

关于协程

可以理解为一个更加轻量级的线程, 具体操作手册:

网络编程

定时器

对于长时间的IO操作, 定时器是至关重要的, Linux的C语言系统调用函数中, 主要有三种类型的定时模式, 分别是:

但是, 在boost::asio中, 有与异步IO更匹配的定时器机制. 直接参考文档:
https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/timers.html

一般的使用方式为:

void handler(boost::system::error_code ec) { ... }
// do something here
io_context i;
// do something here
deadline_timer t(i);
t.expires_from_now(boost::posix_time::milliseconds(400));
t.async_wait(handler);
// do something here
i.run();

信号处理机制

先回顾Linux的信号处理机制: https://blog.csdn.net/qq_35976351/article/details/85524540

给出一个一般的解决方案, 参考自: https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/overview/signals.html

void handler(
    const boost::system::error_code& error,
    int signal_number)
{
  if (!error)
  {
    // A signal occurred.
  }
}

// Construct a signal set registered for process termination.
boost::asio::signal_set signals(io_context, SIGINT, SIGTERM);

// Start an asynchronous wait for one of the signals to occur.
signals.async_wait(handler);

猜你喜欢

转载自blog.csdn.net/qq_35976351/article/details/90084029