io_context(事件循环/调度器)
定位
boost::asio::io_context是 Asio 的“发动机”:- 负责等待 OS 事件(socket 可读可写、定时器到期等)
- 负责调度并执行你注册的回调(completion handler)
核心用法
io.run():开始处理事件并执行回调;直到没有工作可做才返回。- 多线程模型:可以多个线程同时调用同一个
io_context.run(),实现并发处理回调。 io.stop():请求停止事件循环;通常用于优雅退出(配合 signal)。
关键点
- run() 什么时候返回?
- 当没有任何“未完成的异步操作”且没有“work guard”时,
run()会返回。
- 当没有任何“未完成的异步操作”且没有“work guard”时,
- 多线程 run 的含义
- 回调可能在任意一个
run()线程中执行(并发),除非你用strand限制串行。
- 回调可能在任意一个
- 不要阻塞回调线程
- 在回调里做耗时任务会拖慢整个 I/O;真实项目一般把耗时业务丢到线程池/队列。
executor
io_context有一个executor:io.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){ ... });
}
};
需要满足的条件
- 这个对象必须是由
std::shared_ptr管理的(例如std::make_shared<Session>创建)。 - 不能在构造函数里调用
shared_from_this()(对象还没被 shared_ptr 接管,行为未定义)。 - 不要把
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中操作。

3036

被折叠的 条评论
为什么被折叠?



