Boost::asio async_read两次调用仅触发一次handler的问题排查咨询
问题分析与解决方案
哥们,你这个问题我之前用Boost.Asio的时候也踩过坑,核心问题出在对io_service::run()的工作逻辑理解上,还有bind参数传递的小错误。咱们一步步拆解来看:
直接导致第二次async_read没触发的原因
当你第一次调用connection->io_service->run()时,它会处理完第一个async_read任务后,因为没有其他待执行的异步操作,run()会直接返回,并且把io_service置为停止状态。这时候你再调用run(),它会直接跳过执行——这就是第二次handler没被触发的关键原因。
另外还有两个隐藏的问题:
- 你在bind时传递
connection->ec是按值拷贝的,handler里拿到的是原error_code的副本,不会更新你connection对象里的ec值; - 用
new分配的Data内存没有释放,会造成内存泄漏。
修正你的现有代码(同步调用异步操作方式)
先把你的代码改成可以正常工作的版本,修复上述问题:
#include <boost/asio.hpp> #include <iostream> #include <vector> #include <boost/bind/bind.hpp> using namespace std; using namespace boost::asio; struct Tcp_connection { io_service* io_service; ip::tcp::socket* sock; boost::system::error_code ec; }; int calculateSize(uint8_t Header[]) { if (Header[2] == 0) return Header[3]-4; return (Header[2] * 256 + Header[3])-4; } void read_handler( const boost::system::error_code& ec, std::size_t bytes_transferred) { if (ec) { cout << ec.message() << endl; } else { cout << "Bytes received " << bytes_transferred << endl; } } int receive_packet(Tcp_connection* connection) { uint8_t Header[4]; // 用boost::ref传递ec的引用,确保handler能更新原对象的ec async_read(*connection->sock, buffer(Header, sizeof(Header)), boost::bind(&read_handler, boost::ref(connection->ec), placeholders::bytes_transferred)); connection->io_service->run(); // 先检查读报头是否出错 if (connection->ec) { cout << "Read header failed: " << connection->ec.message() << endl; return -1; } int dataSize = calculateSize(Header); // 用vector管理内存,自动释放避免泄漏 std::vector<uint8_t> Data(dataSize); // 重置io_service,让它可以处理新的异步任务 connection->io_service->reset(); async_read(*connection->sock, buffer(Data), boost::bind(&read_handler, boost::ref(connection->ec), placeholders::bytes_transferred)); connection->io_service->run(); if (connection->ec) { cout << "Read data failed: " << connection->ec.message() << endl; return -1; } // 在这里处理Data中的报文数据 // ... return 0; }
推荐的异步编程方式(链式调用)
上面的写法是把异步操作当成同步来用,其实更符合Boost.Asio设计思想的是链式异步调用——在第一个读操作的handler里启动第二个读操作,这样不需要手动resetio_service,代码也更优雅:
#include <boost/asio.hpp> #include <iostream> #include <vector> #include <boost/bind/bind.hpp> #include <memory> using namespace std; using namespace boost::asio; struct Tcp_connection { io_service* io_service; ip::tcp::socket* sock; }; // 用结构体保存异步操作的上下文,避免栈变量提前销毁 struct ReadContext { std::shared_ptr<Tcp_connection> connection; uint8_t header[4]; std::vector<uint8_t> data; }; int calculateSize(uint8_t Header[]) { if (Header[2] == 0) return Header[3]-4; return (Header[2] * 256 + Header[3])-4; } // 读数据的handler void read_data_handler(const boost::system::error_code& ec, std::size_t bytes_transferred, std::shared_ptr<ReadContext> ctx) { if (ec) { cout << "Read data failed: " << ec.message() << endl; return; } cout << "Received data bytes: " << bytes_transferred << endl; // 处理接收到的数据 // ... } // 读报头的handler,完成后启动读数据操作 void read_header_handler(const boost::system::error_code& ec, std::size_t bytes_transferred, std::shared_ptr<ReadContext> ctx) { if (ec) { cout << "Read header failed: " << ec.message() << endl; return; } cout << "Received header bytes: " << bytes_transferred << endl; int dataSize = calculateSize(ctx->header); ctx->data.resize(dataSize); // 链式启动第二个异步读操作 async_read(*ctx->connection->sock, buffer(ctx->data), boost::bind(&read_data_handler, placeholders::error, placeholders::bytes_transferred, ctx)); } int receive_packet(std::shared_ptr<Tcp_connection> connection) { auto ctx = make_shared<ReadContext>(); ctx->connection = connection; // 启动第一个异步读操作 async_read(*connection->sock, buffer(ctx->header, sizeof(ctx->header)), boost::bind(&read_header_handler, placeholders::error, placeholders::bytes_transferred, ctx)); // run会一直运行直到所有异步操作完成 connection->io_service->run(); return 0; }
这种方式的好处是:
- 不需要手动reset
io_service,所有异步操作是链式触发的,io_service会自动处理直到最后一个任务完成; - 用
std::shared_ptr管理上下文,避免内存泄漏和栈变量提前销毁的问题; - 更贴合异步编程的非阻塞思想,适合后续扩展到多线程场景。
内容的提问来源于stack exchange,提问作者Serhan Erkovan
相关产品推荐
相关产品推荐

