同步tcp客户端不卡死需设超时、用error_code判断connect/read结果、禁nagle算法、先解析域名再连接;异步操作须在handler中手动续发,注意生命周期和线程安全。

asio 同步 TCP 客户端怎么写才不卡死
同步模式下 boost::asio::ip::tcp::socket::connect() 会阻塞,但真正让程序卡住的往往不是 connect 本身,而是没设超时、没检查 error_code、或在主线程里调用阻塞读写又没做异常处理。
- 必须显式设置 socket 选项:调用
socket.set_option(boost::asio::ip::tcp::no_delay(true))避免 Nagle 算法拖慢小包 - 连接前先用
resolver.resolve()解析域名,别直接传字符串给endpoint;否则 DNS 失败时可能抛异常而非返回 error_code -
socket.connect()和socket.read_some()都要传boost::system::error_code&参数,靠ec判断成败,别依赖 try/catch - 示例片段:
boost::asio::io_context io; boost::asio::ip::tcp::socket sock(io); boost::asio::ip::tcp::resolver resolver(io); auto endpoints = resolver.resolve("example.com", "80"); boost::system::error_code ec; sock.connect(*endpoints, ec); if (ec) { /* 处理连接失败 */ }
异步 read/write 为什么只触发一次就停了
这是最常踩的坑:async_read 或 async_write 的完成处理器(handler)执行完后,asio 不会自动发起下一次操作。你得在 handler 里手动再调一次 async_read() 或 async_write(),否则连接就“静默”了。
-
async_read()默认按缓冲区填满才回调,如果对方发的是短消息且没带长度头,容易等不到满缓冲而一直挂起——改用async_read_until(sock, buf, "\n")或配合boost::asio::transfer_at_least(1) - 所有异步操作的 handler 必须是可调用对象,且捕获的变量(比如
shared_ptr<session></session>)要确保生命周期长于 I/O 操作,否则访问已释放内存 - 不要在 handler 里直接 delete this;推荐用
std::enable_shared_from_this+shared_from_this()延续对象生命 - 常见错误现象:
Operation not permitted或Bad file descriptor,多因 socket 已 close 却还在 post 异步操作
如何安全地在多线程中使用 io_context
boost::asio::io_context 本身不是线程安全的,但可以被多个线程调用 run() —— 这是 asio 推荐的并发模型,不是用 mutex 包裹单个 io_context。
- 启动方式:
std::thread t([&io]{ io.run(); });启多个线程,每个都调run(),无需额外锁 - 注意:
io.run()是阻塞直到无待处理事件,所以一般搭配io.stop()控制退出;若想非阻塞轮询,用io.poll_one() - 跨线程提交任务用
post(io, [&](){ /* 代码 */ });,它保证 handler 在某个run()线程中执行,且不会导致竞态 - 千万别在非 io_context 线程里直接调
socket.write_some()—— 这属于同步 I/O,和 io_context 无关,但会破坏异步逻辑的一致性
asio 里怎么处理粘包和拆包
asio 不管应用层协议,它只负责收发字节流。所谓“粘包”是 TCP 特性,不是 asio 的 bug;拆包逻辑必须自己加。
- 最简单方案:每条消息开头写 4 字节大端长度(
htonl(len)),然后用async_read()先读 4 字节,再读指定长度 - 避免手写循环读取,用
boost::asio::async_read(sock, boost::asio::buffer(&len, 4), ...)+boost::asio::async_read(sock, boost::asio::buffer(buf, len), ...) - 如果用
boost::asio::streambuf,记得每次读完后调consume()清除已处理数据,否则下次data()还包含旧内容 - 调试时可用
streambuf.size()查看当前缓存字节数,比裸 char 数组更容易定位读偏移问题
实际写服务端时,accept() 后立刻 async_read()、每次 handler 里解析完消息再 async_read() 下一条——这个循环结构漏掉一环,整个连接就停摆了。细节都在 handler 的调用链里,不在文档首页。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











