Skip to content

Wiki

Boost.Asio Asynchronous IO Library

About 7 min read

Existing English translations were reused. 1 current source blocks remain in Simplified Chinese, without triggering paid translation.

This chapter is intended for learning about robots, ROS2, lower-level computer serial ports, and TCP/UDP communication.
This tutorial deliberately adopts std::bindand temporarily does not use lambda, so that beginners can first clearly see "callback functions, placeholders, member function binding, and asynchronous execution order".


1.Chapter order of this tutorial series

I moved the “timer” earlier because the timer does not depend on serial hardware or the network peer, making it the most suitable for seeing clearly:

  1. io_context.run()Why does it block;
  2. async_wait()Why isn't the callback executed immediately;
  3. The difference between one timer and two timers;
  4. Which thread exactly does the callback function execute in;
  5. std::bindin_1、this、&Class::funcWhat exactly do they mean.

2.Uniform conventions for this tutorial series

2.1.Use the standard library approach consistently.

This tutorial uses standard libraries as much as possible:

#include <chrono>
#include <functional>
#include <memory>
#include <thread>
#include <string>

For example, timer times are uniformly written as:

std::chrono::seconds(1)
std::chrono::milliseconds(100)

Rather than prioritizing writing:

boost::asio::chrono::seconds(1)

2.2.Callbacks uniformly use std::bind

In this tutorial, asynchronous callbacks are written as much as possible as:

timer.async_wait(std::bind(on_timer, std::placeholders::_1));

Member function callbacks are written as:

timer_.async_wait(std::bind(&Printer::on_timer, this, std::placeholders::_1));

读写回调有两个参数时写成:

socket.async_read_some(
    boost::asio::buffer(data_),
    std::bind(&Session::on_read,
              this,
              std::placeholders::_1,
              std::placeholders::_2));

Among them:

std::placeholders::_1
std::placeholders::_2

Indicates "the 1st and 2nd arguments Boost.Asio automatically passed to the callback function when an asynchronous operation completes".

2.3.The error code still uses the type of Boost.Asio .

Do not change this arbitrarily:

const boost::system::error_code& ec

The reason is that the asynchronous callback of Boost.Asio passes the error by default to boost::system::error_code. Later, if you switch to standalone Asio or a standard networking library, then consider the corresponding type.


3.The core idea of Boost.Asio

Boost.Asio It can first be roughly understood as:

io_context = 事件循环 / 调度器
socket / serial_port / timer = IO对象
async_xxx() = 注册一个异步任务
handler = 异步任务完成后执行的回调函数
run() = 开始处理异步任务和回调函数

A minimal asynchronous program looks roughly like this:

boost::asio::io_context io;
boost::asio::steady_timer timer(io, std::chrono::seconds(2));

timer.async_wait(std::bind(on_timer, std::placeholders::_1));

io.run();

The execution logic is not:

async_wait 立刻执行 on_timer

Instead:

async_wait 注册任务
io.run() 进入事件循环
等待 2 秒
timer 到期
io.run() 调用 on_timer
没有任务了
io.run() 返回

4.Build environment

4.1.Ubuntu / Debian

sudo apt update
sudo apt install libboost-all-dev libasio-dev

4.2.Fedora

sudo dnf makecache
sudo dnf install boost-devel asio-devel

5.Why learn timers first?

In robot communication, the most problematic thing is not “whether the API can be called,” but:

  1. When is the callback executed;
  2. On which thread is the callback executed;
  3. When is the object destructed;
  4. When can the buffer data still be used;
  5. run() Why does it get stuck;
  6. run() Why does it return early again;
  7. Why does data race occur in multithreading.

These problems can all first be understood using timers. Once timers are understood, serial ports, TCP, and UDP are essentially just "different events being waited for":

timer 等待时间到期
serial_port 等待串口可读 / 可写
tcp::socket 等待网络可读 / 可写 / 连接完成
udp::socket 等待收到一个数据报

6.What level should be achieved after completing this tutorial?

After completing this, you should be able to:

  1. Understand the official timer / TCP / UDP tutorials for Boost.Asio;
  2. Be able to write ordinary function callbacks and member function callbacks using std::bind;
  3. Be able to explain the blocking and return conditions of io_context.run();
  4. Be able to write asynchronous serial reading of data from a lower-level device;
  5. Be able to write a TCP client / server;
  6. Be able to write a UDP sender / receiver / echo server;
  7. Be able to encapsulate the Asio communication module into a class;
  8. Be able to integrate the communication module into a ROS2 node, instead of writing a blocking infinite loop inside a ROS2 callback.

7.The 5 sentences you most need to remember right now

  1. async_xxx()It only registers an asynchronous task; it does not execute the callback immediately.
  2. io_context.run()is what actually drives the execution of the asynchronous task.
  3. The callback function will only be called in the thread that is executing io_context.run().
  4. Asynchronous buffer, socket, and timer objects must remain alive until the callback finishes executing.
  5. When binding a member function in a class, write std::bind(&Class::func, this, _1, _2).