Linux下使用async_receive_from无法正常接收UDP数据包,Windows正常
问题描述
我编写了一个基于Boost.Asio的UDP C++程序,用于和硬件设备收发消息,在Windows系统运行正常,但移植到Ubuntu 22.04 LTS后,仅偶尔能接收数据包,多数情况下无法正常接收。相关代码片段如下:
JULidar::JULidar(const std::string& local_ip, const std::string& local_port, const std::string& device_ip, const std::string& device_port) : io_context(), socket(io_context), local_endpoint(boost::asio::ip::address::from_string(local_ip), std::stoi(local_port)), device_endpoint(boost::asio::ip::address::from_string(device_ip), std::stoi(device_port)), receive_thr(&JULidar::receive_thread, this), process_thr(&JULidar::process_thread, this), output_flag(false), param_pkg_operated_completed(false), frame_update_completed(false) { try { std::cout << "binding local ip ..." << std::endl; socket.open(boost::asio::ip::udp::v4()); socket.bind(local_endpoint); asyncRecvFrom(); } catch (boost::system::system_error) { std::cout << "local network config error, please modify the ip & port above." << std::endl; } } JULidar::~JULidar() { socket.close(); io_context.stop(); receive_thr.interrupt(); receive_thr.join(); process_thr.interrupt(); process_thr.join(); } void JULidar::asyncRecvFrom() { boost::system::error_code error; socket.async_receive_from( boost::asio::buffer(udpBuffer), device_endpoint, boost::bind( &JULidar::recvHandler, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } void JULidar::recvHandler(const boost::system::error_code& error, size_t bytes_received) { if (bytes_received != 0) { // ... 数据处理逻辑 } asyncRecvFrom(); } void JULidar::receive_thread() { while (1) { try { io_context.run(); boost::this_thread::interruption_point(); } catch (...) { break; } } std::cout << "recv_thr ending..." << std::endl; }
问题根源分析
io_context.run()的运行逻辑错误
io_context.run()在所有异步操作完成或被stop()后会返回,且后续再次调用run()不会自动重启,必须先调用restart()。当前代码的receive_thread循环中,第一次run()执行完毕后,后续的run()不会处理新的异步接收请求,导致仅能处理第一次触发的数据包,后续无法响应新数据。固定接收端点导致的过滤问题
async_receive_from中传入了固定的device_endpoint,这会让socket仅接收来自该特定IP和端口的数据包。但Linux网络栈对源端口的校验更严格,若设备发送数据包时的源端口与你指定的device_port不一致(比如设备动态使用源端口),数据包会被直接丢弃;而Windows可能对此容忍度更高,导致跨平台差异。错误处理缺失
recvHandler完全忽略了error参数,即使接收操作出现错误(如socket被关闭、网络异常),仍会继续发起新的异步接收,可能导致无效操作堆积,或掩盖关键错误信息。析构函数的资源释放顺序错误
先调用socket.close()再停止io_context,可能导致io_context在处理未完成的异步操作时触发异常,干扰线程的正常终止流程。
修复方案
1. 使用work_guard保持io_context持续运行
添加executor_work_guard防止io_context.run()在无异步操作时返回,简化线程逻辑:
// 在类中添加成员变量 boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work_guard; // 构造函数初始化 JULidar::JULidar(const std::string& local_ip, const std::string& local_port, const std::string& device_ip, const std::string& device_port) : io_context(), socket(io_context), local_endpoint(boost::asio::ip::address::from_string(local_ip), std::stoi(local_port)), device_endpoint(boost::asio::ip::address::from_string(device_ip), std::stoi(device_port)), work_guard(boost::asio::make_work_guard(io_context)), // 初始化work_guard receive_thr(&JULidar::receive_thread, this), process_thr(&JULidar::process_thread, this), output_flag(false), param_pkg_operated_completed(false), frame_update_completed(false) { // 原有构造逻辑不变 } // 修改receive_thread void JULidar::receive_thread() { try { io_context.run(); } catch (const boost::thread_interrupted&) { // 线程中断处理 } catch (...) { // 其他异常处理 } std::cout << "recv_thr ending..." << std::endl; }
2. 动态获取发送方端点,按需过滤
将固定的device_endpoint替换为动态填充的端点,接收所有数据包后再手动校验是否来自目标设备:
// 在类中添加成员变量 boost::asio::ip::udp::endpoint remote_endpoint; // 修改asyncRecvFrom void JULidar::asyncRecvFrom() { socket.async_receive_from( boost::asio::buffer(udpBuffer), remote_endpoint, // 使用动态端点 boost::bind( &JULidar::recvHandler, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } // 修改recvHandler,添加错误处理和设备校验 void JULidar::recvHandler(const boost::system::error_code& error, size_t bytes_received) { if (!error && bytes_received != 0) { // 校验是否来自目标设备 if (remote_endpoint.address() == device_endpoint.address() && remote_endpoint.port() == device_endpoint.port()) { // ... 原有数据处理逻辑 } } else if (error != boost::asio::error::operation_aborted) { std::cerr << "Receive error: " << error.message() << std::endl; } // 仅在非操作中止的情况下,继续发起下一次接收 if (!error || error != boost::asio::error::operation_aborted) { asyncRecvFrom(); } }
3. 调整析构函数的资源释放顺序
先停止io_context,再关闭socket,确保异步操作正常终止:
JULidar::~JULidar() { io_context.stop(); // 先停止io_context,终止所有异步操作 socket.close(); receive_thr.interrupt(); receive_thr.join(); process_thr.interrupt(); process_thr.join(); }
总结
通过上述修复,解决了Linux下io_context无法持续处理异步请求、固定端点过滤导致的数据包丢失、错误处理缺失以及资源释放顺序错误的问题,可让程序在Linux上达到与Windows一致的运行效果。
内容的提问来源于stack exchange,提问作者Jiahao Zhu

