You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux下使用async_receive_from无法正常接收UDP数据包,Windows正常

UDP程序在Linux下偶尔接收失败的问题排查与解决

问题描述

我编写了一个基于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;
}

问题根源分析

  1. io_context.run()的运行逻辑错误
    io_context.run()在所有异步操作完成或被stop()后会返回,且后续再次调用run()不会自动重启,必须先调用restart()。当前代码的receive_thread循环中,第一次run()执行完毕后,后续的run()不会处理新的异步接收请求,导致仅能处理第一次触发的数据包,后续无法响应新数据。

  2. 固定接收端点导致的过滤问题
    async_receive_from中传入了固定的device_endpoint,这会让socket仅接收来自该特定IP和端口的数据包。但Linux网络栈对源端口的校验更严格,若设备发送数据包时的源端口与你指定的device_port不一致(比如设备动态使用源端口),数据包会被直接丢弃;而Windows可能对此容忍度更高,导致跨平台差异。

  3. 错误处理缺失
    recvHandler完全忽略了error参数,即使接收操作出现错误(如socket被关闭、网络异常),仍会继续发起新的异步接收,可能导致无效操作堆积,或掩盖关键错误信息。

  4. 析构函数的资源释放顺序错误
    先调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 19:02:05