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
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~:
#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: