Boost Asio类中async_receive_from内定时器无法触发问题
问题:Boost Asio封装类后UDP代理定时器无法触发
尝试用Boost Asio编写小型UDP代理,需求是收到数据报后,在指定时间触发回调转发数据,同时立即准备接收新数据报。非类封装版本可正常运行,定时器能如期触发;但封装为类后,能接收数据报并打印日志,却始终无法触发定时器。
可正常运行的非类版本代码
#include <iostream> #include <boost/asio.hpp> void receive(udp::socket &socket, io_context &ioContext) { udp::endpoint receive_endpoint; std::array<char, 8192> buf; socket.async_receive_from(buffer(buf, buf.size()), receive_endpoint, [&](const boost::system::error_code& error, std::size_t bytesReceived) { if (!error && bytesReceived > 0) { std::cout << "Got a package" << std::endl; boost::asio::steady_timer timer = boost::asio::steady_timer(ioContext, boost::asio::chrono::milliseconds(100)); timer.async_wait([&](const boost::system::error_code& ec) { std::cout << "Timer hit" << std::endl; }); receive(socket, ioContext); } else { std::cout << "Error receiving data from client" << std::endl; } }); } int main() { boost::asio::io_context ioContext; udp::socket socket(ioContext); socket.open(udp::v4()); udp::endpoint localEndpoint(ip::address::from_string("127.0.0.1"), 12345); socket.bind(localEndpoint); receive(socket, ioContext); ioContext.run(); return 0; }
封装为类后的主文件代码
#include "udp_class.h" #include <iostream> #include <boost/asio.hpp> int main() { boost::asio::io_context ioContext; Proxy my_proxy(ioContext); my_proxy.start(); ioContext.run(); return 0; }
类定义(udp_class.h)
#pragma once #define BOOST_ASIO_ENABLE_HANDLER_TRACKING #include <iostream> #include <boost/asio.hpp> #include <chrono> using namespace boost::asio; using ip::udp; using std::chrono::milliseconds; class Proxy { public: Proxy(io_context& ioContext): localSocket_(ioContext) { }; void start() { localSocket_.open(udp::v4()); localSocket_.bind(udp::endpoint(ip::address::from_string("127.0.0.1"), 12345)); receive(); }; private: void receive() { udp::endpoint receive_endpoint; std::array<char, 8192> buf; localSocket_.async_receive_from(buffer(buf, buf.size()), receive_endpoint, [&](const boost::system::error_code& error, std::size_t bytesReceived) { if (!error && bytesReceived > 0) { std::cout << "Got a package" << std::endl; boost::asio::steady_timer timer = boost::asio::steady_timer(ioContext, boost::asio::chrono::milliseconds(100)); timer.async_wait([&](const boost::system::error_code& ec) { std::cout << "Timer hit" << std::endl; }); receive(); } else { std::cout << "Error receiving data from client" << std::endl; } }); } io_context ioContext; udp::socket localSocket_; };
Boost Asio调试输出
@asio|1720035449.638484|0*1|socket@0x16b0ef4f8.async_receive_from @asio|1720035449.638495|.1|non_blocking_recvfrom,ec=system:35,bytes_transferred=0 @asio|1720035451.692791|.1|non_blocking_recvfrom,ec=system:0,bytes_transferred=12 @asio|1720035451.692827|>1|ec=system:0,bytes_transferred=12 @asio|1720035451.692951|1*2|deadline_timer@0x16b0ef038.async_wait @asio|1720035451.692976|1*3|socket@0x16b0ef4f8.async_receive_from @asio|1720035451.692987|.3|non_blocking_recvfrom,ec=system:35,bytes_transferred=0 @asio|1720035451.692992|1|deadline_timer@0x16b0ef038.cancel @asio|1720035451.692998|<1|
问题分析与解决方案
核心错误点
- io_context不匹配:类中定义了成员变量
io_context ioContext,但构造函数中localSocket_使用的是传入的io_context(main函数中的实例),而定时器却绑定到类自己的ioContext上。main函数中只run了传入的那个io_context,类成员的ioContext从未被调度,因此定时器的异步等待永远不会被执行。 - 定时器生命周期过短:定时器是
receive函数中的局部变量,当receive函数执行完毕(异步回调返回后),定时器对象被销毁,析构函数会自动cancel所有未完成的异步操作,这也对应了调试输出中的deadline_timer@0x16b0ef038.cancel日志。 - Lambda引用捕获风险:回调中使用
[&]引用捕获局部变量,可能导致局部变量销毁后引用失效,引发未定义行为。
修改后的类代码
#pragma once #define BOOST_ASIO_ENABLE_HANDLER_TRACKING #include <iostream> #include <boost/asio.hpp> #include <chrono> #include <memory> using namespace boost::asio; using ip::udp; using std::chrono::milliseconds; class Proxy { public: // 保存传入io_context的引用,而非创建新实例 Proxy(io_context& ioContext): ioContext_(ioContext), localSocket_(ioContext) { }; void start() { localSocket_.open(udp::v4()); localSocket_.bind(udp::endpoint(ip::address::from_string("127.0.0.1"), 12345)); receive(); }; private: void receive() { udp::endpoint receive_endpoint; // 缓冲区用shared_ptr延长生命周期,避免异步操作时失效 auto buf = std::make_shared<std::array<char, 8192>>(); localSocket_.async_receive_from(buffer(*buf, buf->size()), receive_endpoint, // 捕获this和buf,避免引用失效 [this, buf](const boost::system::error_code& error, std::size_t bytesReceived) { if (!error && bytesReceived > 0) { std::cout << "Got a package" << std::endl; // 用shared_ptr管理定时器,延长生命周期至回调完成 auto timer = std::make_shared<steady_timer>(ioContext_, milliseconds(100)); timer->async_wait([timer](const boost::system::error_code& ec) { if (!ec) { // 忽略被cancel的情况 std::cout << "Timer hit" << std::endl; } }); receive(); } else { std::cout << "Error receiving data from client" << std::endl; } }); } io_context& ioContext_; // 改为引用,复用main中的io_context udp::socket localSocket_; };
修改说明
- 类中保存传入的
io_context引用,确保所有异步操作都绑定到同一个被run的io_context实例。 - 使用
std::shared_ptr管理定时器和缓冲区,延长它们的生命周期,避免局部变量销毁导致异步操作被cancel或引用失效。 - Lambda捕获改为值捕获
this和buf,避免引用捕获带来的悬空引用问题。
内容的提问来源于stack exchange,提问作者Jon Lachmann
相关产品推荐
相关产品推荐

