不能直接用 io_context::run() 启动异步服务器,因为它是阻塞调用且仅在任务队列为空时退出,而服务器需持续接受连接;单线程 run() 易被耗时 handler 卡住,应多线程调用 run() 并用 shared_ptr 管理 session 生命周期、成员变量存储读缓冲、shared_ptr 包装写缓冲以确保内存安全。

为什么不能直接用 io_context::run() 启动异步服务器
因为 io_context::run() 是阻塞调用,且只在任务队列为空时才退出——而服务器需要持续接受新连接,必须保持 io_context 活跃。如果只调一个线程调用 run(),一旦某个 handler 耗时过长(比如同步磁盘 I/O),整个事件循环就卡住。
- 正确做法是提前调用
io_context::post()或io_context::dispatch()注入 accept 循环起点 - 推荐启动多个线程调用
run(),例如std::thread{[&ctx]{ ctx.run(); }},避免单点阻塞 - 注意:
io_context本身是线程安全的,但 handler 内部访问的共享资源(如连接列表)仍需加锁或用strand
如何正确实现 accept 循环并避免重复监听
常见错误是每次 accept 完成后,忘记重新调用 acceptor.async_accept(),导致服务器只能处理一个连接就停止响应。
- 必须在每个
async_accept的 completion handler 末尾,再次发起下一轮async_accept - 不要在构造
tcp::acceptor后只调一次async_accept - 推荐封装为成员函数(如
do_accept()),并在 handler 中递归调用自身 - 监听 socket 需设置
reuse_address选项,否则重启服务时可能报Address already in use
acceptor_.open(tcp::v4());
acceptor_.set_option(tcp::socket::reuse_address(true));
acceptor_.bind({tcp::v4(), port_});
acceptor_.listen();
do_accept(); // 启动第一轮
怎么管理多个 client socket 的生命周期
裸指针或栈对象传给异步 handler 极易引发 use-after-free:handler 执行时,对应对象可能已被析构。
- 必须用
std::shared_ptr包裹每个连接会话对象 - 在
async_accepthandler 中创建shared_ptr,再把该指针传给后续所有 handler(包括async_read、async_write) -
session 对象内部持有
tcp::socket和io_context::strand(可选),确保回调串行执行 - 关闭连接时调
socket_.close()并清空 shared_ptr 引用,让内存自动回收
异步读写时 buffer 怎么传才安全
Boost.Asio 不复制 buffer 内容,而是保存其地址和长度供底层使用。若传入栈变量地址(如 std::array),handler 执行时该变量早已销毁。
立即学习“C++免费学习笔记(深入)”;
- 读 buffer 必须是 session 对象的成员变量(如
std::array)read_buffer_ - 写 buffer 推荐用
std::shared_ptr<:vector>>,在 write handler 触发前确保它不被释放 - 切勿在 lambda 中捕获局部 buffer 变量(
[buf] { ... }),应捕获shared_from_this()后访问成员 - 使用
boost::asio::buffer(buf_, size)包装,而非裸指针 + 长度
最易被忽略的是:即使用了 strand,也不能绕过 buffer 生命周期检查——strand 只保顺序,不保内存存活。











