async_accept一接受连接就立刻断开,大概率是tcp_connection对象生命周期失控;Boost.Asio异步操作不持有对象所有权,必须用shared_ptr确保其在回调执行期间有效。

async_accept 为什么一接受连接就立刻断开?
常见现象是客户端连上后瞬间被关闭,async_accept 回调刚执行完连接就没了——大概率是 tcp_connection 对象生命周期失控。Boost.Asio 的异步操作不持有对象所有权,你必须确保 tcp_connection 实例在回调执行期间始终有效。
典型错误写法:tcp_connection conn(io_context, socket) 放在 accept_handler 栈上,回调返回即析构,底层 socket 被关闭。
- 正确做法:用
std::shared_ptr<tcp_connection></tcp_connection>管理连接对象,在async_accept成功后make_shared并传入回调 - 务必在
tcp_connection构造时调用socket_.non_blocking(false),避免后续async_read因 socket 处于非阻塞模式而立即失败 - 不要在
tcp_connection的构造函数里直接调用async_read,应在start()这类显式方法中触发,确保对象完全构造完毕
如何让 async_read 持续读取直到遇到特定分隔符?
Boost.Asio 没有内置的“按行读取”或“按分隔符读取”的异步原语,async_read_until 是最接近的方案,但它依赖 dynamic_buffer 和预分配缓冲区,容易因缓冲区不足导致截断或死锁。
推荐组合使用 async_read + 自定义解析逻辑,尤其适合处理自定义协议(如 "LEN:123|DATA:..."):
立即学习“C++免费学习笔记(深入)”;
- 用
boost::asio::streambuf作为接收缓冲区,每次async_read尽可能填满它 - 在回调中调用
streambuf.data()获取boost::asio::const_buffer,再用boost::asio::buffer_cast<const char>(...)</const>转为原始指针进行扫描 - 若未找到分隔符,保留已读数据(
streambuf.commit(n)),再次async_read;若找到,用streambuf.consume(n)清除已处理部分 - 注意:不要对
streambuf频繁commit/consume小段数据,会引发内存碎片;建议单次读取至少 1KB 起
多个 io_context 与线程池协作时,post 和 dispatch 选哪个?
当用多线程跑 io_context::run() 时,从外部线程向 asio 投递任务,post 和 dispatch 行为差异极大,错用会导致竞态或意外同步阻塞。
-
post总是把操作放入队列,由任意一个正在run()的线程异步执行,安全且推荐用于绝大多数场景 -
dispatch会优先尝试在**当前线程**执行(如果当前线程正在该io_context的run()中),否则退化为post;但若你在工作线程里误调用dispatch到主线程的io_context,而主线程并未运行run(),它就变成同步调用,卡住工作线程 - 跨线程发通知(如连接关闭日志、统计上报)一律用
post,别图省事用dispatch - 若需确保回调在某个固定线程(如专用 IO 线程)执行,可给该线程的
io_context绑定strand,再用strand.post(...)
Windows 下 accept 失败报 error_code 995 怎么办?
error_code 995 即 ERROR_OPERATION_ABORTED,不是网络错误,而是操作被主动取消。常见于以下情况:
- 在
async_accept回调还没执行完时,又对同一个acceptor调用了close()或cancel() - 服务器 shutdown 流程中,先
acceptor.close(),再等待所有连接结束,但没等async_accept回调返回就强行退出 - 使用
boost::asio::steady_timer做超时控制时,timer 过期回调里调用了acceptor.cancel(),但此时 accept 操作其实已完成,只是回调尚未调度——cancel 仍会触发 995
真正可靠的 shutdown 方式:调用 acceptor.close() 后,不再发起新 async_accept;等待当前 pending 的 accept 回调全部完成(可通过引用计数或 std::atomic 跟踪),再销毁 io_context。
995 不需要重试或告警,只要确保你的逻辑不依赖 accept 回调一定成功即可——它只是告诉你“这个异步操作没走到完成那步就被终止了”。


















