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

基于ASIO的UDP消息收发:发送成功但循环接收失败

UDP异步收发故障排查(Asio库)

我用Asio库实现了UDP消息收发功能,发送的Header+Body消息仅用于测试,复用了为TCP编写的Message结构。目前客户端能成功发送消息(输出"Sent"日志确认),但服务器循环调用UDP::Recieve函数时无法接收消息。服务器Socket绑定6001端口,客户端Socket绑定6000端口,客户端向6001端口发送消息后,服务器无任何接收日志输出。相关代码如下:

void UDP::Recieve(Func inRecieve) {
    mUDPSocket.async_receive(
         asio::buffer(&mTemporaryMessageBuffer.mHeader, sizeof(MessageHeader)),
         [&](std::error_code ec, size_t inSize) {
             if (not ec) {
                 std::cout << "Received:" << inSize << "\n";
                 auto &msg = mTemporaryMessageBuffer;
                 msg.mBody.resize(msg.size());
                 mUDPSocket.async_receive(
                      asio::buffer(mTemporaryMessageBuffer.mBody.data(), msg.mBody.size()),
                      [&](std::error_code ec, size_t inSize) {
                          if (not ec) {
                              std::cout << "Received:" << inSize << "\n";
                              inRecieve(mTemporaryMessageBuffer);
                          } else {
                              std::cout << "ERROR:" << ec.message() << "\n";
                          }
                          std::flush(std::cout);
                      });
             } else {
                 std::cout << "ERROR:" << ec.message() << "\n";
             }
             std::flush(std::cout);
         });
}
void UDP::Send(const Message &inMessage, std::string inDestination, int inPort) {
    asio::ip::udp::endpoint Remote(asio::ip::address::from_string(s(inDestination)), inPort);
    mUDPSocket.async_send_to(asio::buffer(&inMessage.mHeader, sizeof(MessageHeader)),
                             Remote,
                             [&](std::error_code ec, size_t inSize) {
                                 if (not ec) {
                                     std::cout << "Sent" << inSize << "\n";
                                 } else {
                                     std::cout << "ERROR:" << ec.message() << "\n";
                                 }
                                 std::flush(std::cout);
                             });
    mUDPSocket.async_send_to(asio::buffer(inMessage.mBody.data(), inMessage.mBody.size()),
                             Remote,
                             [&](std::error_code ec, size_t inSize) {
                                 if (not ec) {
                                     std::cout << "Sent" << inSize << "\n";
                                 } else {
                                     std::cout << "ERROR:" << ec.message() << "\n";
                                 }
                                 std::flush(std::cout);
                             });
}
} // namespace Jkr::Network

核心问题与修复方案

1. UDP接收API误用

UDP是无连接协议,async_receive仅适用于已通过connect绑定固定端点的UDP套接字。你的服务器未提前与客户端建立连接,必须使用async_receive_from来接收任意发送方的消息,同时获取发送方端点信息。

2. 分段收发的风险

UDP基于报文传输,你将Header和Body拆分为两次async_send_to发送,会生成两个独立UDP报文;接收时也分两次读取,可能出现报文顺序错乱、部分丢失的情况,导致消息结构破坏。正确做法是将Header+Body合并为一个缓冲区一次性发送。

3. Lambda引用捕获的生命周期问题

代码中用[&]捕获局部变量引用,Recieve函数返回后,inRecieve等变量会被销毁,lambda内的引用会变成悬空引用,触发未定义行为。需改用值捕获或延长变量生命周期。


修复后的代码示例

接收函数修正

void UDP::Recieve(Func inRecieve) {
    // 用shared_ptr保存发送方端点,避免生命周期问题
    auto remote_endpoint = std::make_shared<asio::ip::udp::endpoint>();
    mUDPSocket.async_receive_from(
         asio::buffer(&mTemporaryMessageBuffer.mHeader, sizeof(MessageHeader)),
         *remote_endpoint,
         [this, inRecieve, remote_endpoint](std::error_code ec, size_t inSize) {
             if (!ec) {
                 std::cout << "Received header:" << inSize << "\n";
                 auto &msg = mTemporaryMessageBuffer;
                 // 假设Header的size字段表示Body长度
                 msg.mBody.resize(msg.mHeader.size);
                 mUDPSocket.async_receive_from(
                      asio::buffer(msg.mBody.data(), msg.mBody.size()),
                      *remote_endpoint,
                      [this, inRecieve, remote_endpoint, &msg](std::error_code ec, size_t inSize) {
                          if (!ec) {
                              std::cout << "Received body:" << inSize << "\n";
                              inRecieve(msg);
                              // 递归调用维持接收循环
                              Recieve(inRecieve);
                          } else {
                              std::cout << "ERROR receiving body:" << ec.message() << "\n";
                          }
                          std::flush(std::cout);
                      });
             } else {
                 std::cout << "ERROR receiving header:" << ec.message() << "\n";
             }
             std::flush(std::cout);
         });
}

发送函数修正

void UDP::Send(const Message &inMessage, std::string inDestination, int inPort) {
    asio::ip::udp::endpoint Remote(asio::ip::address::from_string(inDestination), inPort);
    
    // 合并Header和Body为完整缓冲区
    std::vector<char> full_buffer;
    full_buffer.reserve(sizeof(MessageHeader) + inMessage.mBody.size());
    // 复制Header
    std::copy_n(reinterpret_cast<const char*>(&inMessage.mHeader), sizeof(MessageHeader), std::back_inserter(full_buffer));
    // 复制Body
    std::copy(inMessage.mBody.begin(), inMessage.mBody.end(), std::back_inserter(full_buffer));
    
    mUDPSocket.async_send_to(
         asio::buffer(full_buffer),
         Remote,
         [](std::error_code ec, size_t inSize) {
             if (!ec) {
                 std::cout << "Sent total:" << inSize << "\n";
             } else {
                 std::cout << "ERROR sending:" << ec.message() << "\n";
             }
             std::flush(std::cout);
         });
}

额外注意事项

  • 服务器套接字必须提前完成绑定(如mUDPSocket.bind(asio::ip::udp::endpoint(asio::ip::udp::v4(), 6001))),且绑定操作无错误。
  • 如需持续监听,需在每次接收完成后递归调用Recieve,维持异步接收链。
  • 类成员mTemporaryMessageBuffer需考虑线程安全,避免多个异步接收操作同时修改。

内容的提问来源于stack exchange,提问作者Darshan Koirala

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 21:35:11