Asio基础

io_context(事件循环/调度器)

定位

  • boost::asio::io_context 是 Asio 的“发动机”:
    • 负责等待 OS 事件(socket 可读可写、定时器到期等)
    • 负责调度并执行你注册的回调(completion handler)

核心用法

  • io.run():开始处理事件并执行回调;直到没有工作可做才返回。
  • 多线程模型:可以多个线程同时调用同一个 io_context.run(),实现并发处理回调。
  • io.stop():请求停止事件循环;通常用于优雅退出(配合 signal)。

关键点

  1. run() 什么时候返回?
    • 当没有任何“未完成的异步操作”且没有“work guard”时,run() 会返回。
  2. 多线程 run 的含义
    • 回调可能在任意一个 run() 线程中执行(并发),除非你用 strand 限制串行。
  3. 不要阻塞回调线程
    • 在回调里做耗时任务会拖慢整个 I/O;真实项目一般把耗时业务丢到线程池/队列。

executor

  • io_context 有一个 executorio.get_executor()
  • executor 表示“把任务投递到哪里执行”的抽象。socket/timer 都绑定到某个 executor 上。

steady_timer(定时器)

定位

  • boost::asio::steady_timer 是基于单调时钟(steady clock)的异步定时器:
    • 常用于:连接超时、心跳、重试、周期性统计打印等
  • “steady” 意味着不受系统时间调整影响(比 system_clock 更适合超时逻辑)。

构造函数

  • steady_timer(io):绑定到 io 的 executor(即 io.get_executor()
  • steady_timer(socket.get_executor()):绑定到 socket 的 executor
    典型目的:让 timer 与这个连接相关的回调都在同一执行上下文里,更方便配合 strand 做串行化。

常见用法

1. 一次性超时

timer.expires_after(10s);
timer.async_wait(handler);

2. 周期性任务(每秒打印一次)

回调里再 schedule 下一次(递归调度):

void tick(){
  timer.expires_after(1s);
  timer.async_wait([&](ec){
    if(!ec){ do_something(); tick(); }
  });
}

3. “续命式”空闲超时(idle timeout)

  • 每次活动(读/写成功)就重新 expires_after() + async_wait()
  • 旧的 wait 会被取消,旧回调收到:operation_aborted(这是正常现象,要忽略)

取消与错误码

  • timer.cancel() 会让挂起的 async_wait 回调尽快执行,并返回 operation_aborted
  • 处理超时逻辑时要区分:
    • operation_aborted:被重置/取消(常见于续命),不要当成真正超时
    • ec == 0:真正到点了

strand(串行化执行器,解决并发回调问题)

定位

  • strand 的作用是:保证提交到同一 strand 的 handler 不会并发执行
  • 在多线程 io_context.run() 模式下非常关键。

需要解决的问题

如果你用多个线程跑 io_context,同一个连接内可能出现:

  • read 回调和 write 回调同时运行
  • 两个 write 回调同时运行(如果你错误地并发发起写)
  • 共享数据(缓冲区、状态机、队列)发生数据竞争

常见用法

在发起 async 时绑定 handler 到 strand

socket.async_read_some(buf,
  asio::bind_executor(strand_, handler));

让对象天然使用 strand executor

比如把 socket/timer 都构造在 strand executor 上(或统一通过 make_strand 的 executor 构造),让所有操作默认串行。

[!Note]

  • strand 不等于线程:它不创建线程,它只是“串行化规则”。
  • strand 只保证“不会并发执行”,不保证“固定在同一条线程执行”。

[!Warnning]

  • 只给读回调绑 strand、写回调不绑:仍然可能并发。
  • 你以为“一个连接只有一个读一个写就安全”:但 timer 回调、close 逻辑也可能并发,仍需要统一串行化策略。

std::enable_shared_from_this(异步对象生命周期管理)

定位

  • 在 Asio 的异步模型里,async_xxx 会立刻返回,真正执行发生在未来。
  • 如果你的对象(Session)在回调执行前就析构,回调里访问 this 会崩溃。
  • enable_shared_from_this 让你能在成员函数里安全拿到 shared_ptr<this>,把它捕获到回调里,延长生命周期。

标准用法

class Session : public std::enable_shared_from_this<Session> {
  void do_read(){
    auto self = shared_from_this();
    socket.async_read_some(...,
      [this, self](ec,n){ ... });
  }
};

需要满足的条件

  1. 这个对象必须是由 std::shared_ptr 管理的(例如 std::make_shared<Session> 创建)。
  2. 不能在构造函数里调用 shared_from_this()(对象还没被 shared_ptr 接管,行为未定义)。
  3. 不要把 shared_ptr<this> 存成成员导致循环引用(常见在 Session↔Room 双向引用时),需要用 weak_ptr 打破环。

async_read_some 和 async_read

async_read_some

  • “能读多少算多少”,读到一点数据就回调。
  • 适合:echo、流式协议的增量解析、你自己维护缓冲区/状态机。

async_read + transfer_exactly / transfer_at_least

  • “读够指定条件才回调”:
    • transfer_exactly(N):必须读满 N 字节
    • transfer_at_least(N):至少 N 字节
  • 适合:有明确长度字段、固定长度消息、需要严格消息边界的场景。

错误码模型(error_code)

Asio回调一版是:

(ec, bytes_transferred)

常见ec:

  • asio::error::eof / message “End of file”:对端正常关闭连接(读到 EOF)。
  • asio::error::operation_aborted:操作被取消(timer cancel、socket close、重置 timer 等引起);通常不是“异常”,要按场景判断。
  • connection reset by peer:对端强行断开(RST)。
  • broken pipe(写时):对端已断开你还在写。

写队列

TCP 写操作的典型规则:

  • 同一个 socket 上尽量不要并发发起多个 async_write(否则数据顺序/缓冲管理会乱)
  • 正确做法:维护一个 std::deque<std::string> 写队列
    • 队列空时启动一次 async_write
    • 写完 pop,再写下一条
  • 这个写队列通常必须在 strand 中操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ht巷子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值