c++network-programmingudpboost-asio

UDP Message Sending and Receiving with ASIO: Send Succeeds but Receive Fails in Loop


I have the following code set up for receiving and sending messages in UDP using the Asio library.

Note: The Header + Body message being sent is not meant to be meaningful, I just want to get a clue of at least something being sent and received over UDP, here I used the Message I made for TCP just to test.

I can successfully send the message, however, I cannot receive the message while I loop on the UDP::Recieve function.

I started a socket at the 6001 port, as a server, and the 6000 port as a client, then sent the message from the client to the server, specifying the port as 6001. I get the "Send " log from the following code which confirms my sending. But my server, though listening in a loop using the following code, cannot get the message.

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

Solution

  • As I commented, there are many issues with the code.

    For example, you capture inRecieve by reference, but it's a stack local object, so when you use it in the completion handler you invoke Undefined Behaviour. This alone explains how nothing works reliably.

    The naive fix is to capture a copy, and then indeed it ~works~:

    Live On Coliru

    #include <boost/asio.hpp>
    #include <boost/endian/arithmetic.hpp>
    #include <iostream>
    #include <fmt/ranges.h>
    
    namespace asio = boost::asio;
    using asio::ip::udp;
    namespace Jkr::Network {
        using Size = boost::endian::big_uint32_t;
    
        struct MessageHeader {
            Size mId, mSize;
        };
    
        struct Message {
            MessageHeader     mHeader;
            std::vector<char> mBody;
            size_t            size() const { return mHeader.mSize; }
        };
    
        class UDP {
          public:
            using Func = std::function<void(Message const&)>;
            UDP(asio::any_io_executor ex, uint16_t port) : mUDPSocket(ex, {{}, port}) {}
    
            void Recieve(Func inRecieve);
            void Send(Message const& inMessage, std::string inDestination, int inPort);
    
          private:
            udp::socket mUDPSocket;
            Message     mIncoming;
        };
    
        void UDP::Recieve(Func inRecieve) {
            mUDPSocket.async_receive(
                asio::buffer(&mIncoming.mHeader, sizeof(MessageHeader)),
                [&, inRecieve = std::move(inRecieve)](std::error_code ec, size_t inSize) {
                    if (not ec) {
                        std::cout << "Received:" << inSize << std::endl;
                        mIncoming.mBody.resize(mIncoming.size());
                        mUDPSocket.async_receive( //
                            asio::buffer(mIncoming.mBody),
                            [&, inRecieve = std::move(inRecieve)](std::error_code ec, size_t inSize) {
                                if (not ec) {
                                    std::cout << "Received:" << inSize << std::endl;
                                    if (inRecieve)
                                        inRecieve(mIncoming);
                                } else {
                                    std::cout << "ERROR:" << ec.message() << std::endl;
                                }
                            });
                    } else {
                        std::cout << "ERROR:" << ec.message() << std::endl;
                    }
                });
        }
    
        void UDP::Send(Message const& inMessage, std::string inDestination, int inPort) {
            asio::ip::udp::endpoint peer(asio::ip::make_address(inDestination), inPort);
    
            mUDPSocket.async_send_to( //
                asio::buffer(&inMessage.mHeader, sizeof(MessageHeader)), peer,
                [&](std::error_code ec, size_t inSize) {
                    if (not ec) {
                        std::cout << "Sent:" << inSize << std::endl;
                    } else {
                        std::cout << "ERROR:" << ec.message() << std::endl;
                    }
                });
            mUDPSocket.async_send_to( //
                asio::buffer(inMessage.mBody.data(), inMessage.mBody.size()), peer,
                [&](std::error_code ec, size_t inSize) {
                    if (not ec) {
                        std::cout << "Sent:" << inSize << std::endl;
                    } else {
                        std::cout << "ERROR:" << ec.message() << std::endl;
                    }
                });
        }
    } // namespace Jkr::Network
    
    int main() {
        asio::io_context io;
        Jkr::Network::UDP server(io.get_executor(), 6001);
        Jkr::Network::UDP client(io.get_executor(), 6000);
    
        server.Recieve([](Jkr::Network::Message const& inMessage) {
            fmt::print("Received: header {{{{{},{}}} body {}}}\n", //
                       inMessage.mHeader.mId.value(),              //
                       inMessage.mHeader.mSize.value(),            //
                       inMessage.mBody);
        });
    
        client.Send({{1, 5}, {'H', 'e', 'l', 'l', 'o'}}, "127.0.0.1", 6001);
        io.run();
    }
    

    Possible output:

    Sent:16
    Sent:5
    Received:16
    Received:5
    Received: header {{1,5} body ['H', 'e', 'l', 'l', 'o']}
    

    Local demonstration:

    enter image description here