在 Linux 上使用 async_receive_from 后无法接收 UDP 数据包,但可以在 Windows 上运行

Cannot receive UDP packets after using async_receive_from on Linux, but can work on Windows

提问人:Jiahao Zhu 提问时间:9/20/2023 最后编辑:Jiahao Zhu 更新时间:9/20/2023 访问量:33

问:

我现在正在编写一个C++程序,该程序使用UDP通过硬件设备发送和接收消息。该程序在 Windows 上运行良好,但是当我在 Linux(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,并不断从设备终结点接收消息。每次数据包到达时,函数 recvHandler() 都会执行一些操作。它在 Windows 上按预期工作。为什么它在 Linux 上不起作用?

如果有人能帮忙,真的很感激!!

如果该程序可以像在 Windows 上一样工作,那就太好了。

C++ Linux 套接字 UDP boost-ASIO

评论

0赞 Quimby 9/20/2023
“效果不好”对问题的描述还不够好。您是否尝试过使用调试器单步执行代码?
0赞 Jiahao Zhu 9/20/2023
@Quimby 对不起,描述不清楚。我想表达的是,代码有时有效,但在其他情况下不起作用。我尝试了调试器,在我的代码中,套接字正在从远程终结点侦听。如果有任何 UDP 数据包传入,它将被处理并输出一些结果,否则什么都不会发生。当我使用调试器时,它的工作方式就像没有数据包来一样。但我知道我已经发送了一些消息,这些数据包可能会被 WireShark 捕获。

答:

0赞 sehe 9/20/2023 #1
  1. 您正在以一种非典型(“错误”)的方式使用执行上下文。例如,在这个循环中

    while (1) {
        try {
            io_context.run();
            boost::this_thread::interruption_point();
        } catch (...) {
            break;
        }
    }
    

    只有当预计会用完工作时,整个人才有用。但是,如果这是真的,则代码是错误的,因为(或,以前)从未被调用过,这将是必需的。interruption_point()io_context.run()io_context.restart()reset()

  2. 在打开套接字/发布第一个异步工作之前创建的线程也存在问题。这意味着可能立即用完工作,即在工作开始之前。io_context

  3. Linux 和 Windows 之间的行为差异可以用时序差异来解释。

    请记住,UDP 不保证交付,因此如果您的机器“繁忙”,一些数据包丢失是意料之中的。


我会使用工作守卫重写逻辑,以避免上下文耗尽工作:

Live On Coliru(科里鲁生活公寓)

#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
#include <boost/thread.hpp>
#include <iomanip>
#include <iostream>

namespace asio = boost::asio;
using asio::ip::udp;
using boost::system::error_code;

struct JULidar {
    JULidar(std::string const& local_ip, std::string const& local_port, //
            std::string const& device_ip, std::string const& device_port);

    ~JULidar();

  private:
    void asyncRecvFrom();
    void recvHandler(error_code error, size_t bytes_received);
    void receive_thread();
    void process_thread() {
        // TODO
    }

    using Executor = asio::io_context::executor_type;
    using Work = asio::executor_work_guard<Executor>;

    asio::io_context io_context;
    Work             work{io_context.get_executor()};

    udp::socket             socket{io_context};
    udp::endpoint           local_endpoint, device_endpoint;
    std::array<char, 65000> udpBuffer;

    std::thread      receive_thr, process_thr;
    std::atomic_bool output_flag{false};
    std::atomic_bool param_pkg_operated_completed{false};
    std::atomic_bool frame_update_completed{false};
};

JULidar::JULidar(std::string const& local_ip, std::string const& local_port, std::string const& device_ip,
                 std::string const& device_port) try
    : local_endpoint(asio::ip::address::from_string(local_ip), static_cast<uint16_t>(std::stoi(local_port)))
    , device_endpoint(asio::ip::address::from_string(device_ip),
                      static_cast<uint16_t>(std::stoi(device_port)))
    , receive_thr(&JULidar::receive_thread, this)
    , process_thr(&JULidar::process_thread, this) //
{
    std::cout << "binding local ip ..." << std::endl;
    socket.open(local_endpoint.protocol());
    socket.bind(local_endpoint);
    asyncRecvFrom();
} catch (boost::system::system_error const&) {
    std::cout << "local network config error, please modify the ip & port above." << std::endl;
}

JULidar::~JULidar() {
    post(socket.get_executor(), [this] { socket.cancel(); });
    work.reset(); // allow the context to run out of work

    if (receive_thr.joinable())
        receive_thr.join();
    if (process_thr.joinable())
        process_thr.join();
}

void JULidar::asyncRecvFrom() {
    using namespace std::placeholders;
    socket.async_receive_from(asio::buffer(udpBuffer), device_endpoint,
                              std::bind(&JULidar::recvHandler, this, _1, _2));
}

void JULidar::recvHandler(error_code error, size_t bytes_received) {
    std::cerr << "recvHandler(" << error.message() << ", " << bytes_received << ")" << std::endl;
    if (!error.failed()) {
        if (bytes_received != 0) {
            // ...
        }
        asyncRecvFrom();
    }
}

void JULidar::receive_thread() {
    for (;;) {
        try {
            io_context.run();
            break; // exited normally
        } catch (std::exception const& e) {
            std::cerr << "[receive_thread] " << e.what() << std::endl;
        } catch (...) {
            std::cerr << "[receive_thread] unknown exception" << std::endl;
        }
    }
    std::cout << "receive_thread ending..." << std::endl;
}

int main() {
    {
        JULidar lidar("127.0.0.1", "8989", "127.0.0.1", "8990");

        using namespace std::literals;
        std::this_thread::sleep_for(30s);
    } // destructor will shutdown io_context
}

本地演示

enter image description here

评论

0赞 Jiahao Zhu 9/21/2023
非常感谢!我已经在这个问题上工作了好几天,现在它起作用了。也许我需要回顾一下套接字编程。