UDP
Learn how to send and receive datagrams using UDP.
UDP vs TCP
| TCP | UDP |
|---|---|
Connection-oriented |
Connectionless |
Reliable, ordered delivery |
Best-effort, may lose or reorder |
Stream of bytes |
Discrete datagrams |
|
|
Use UDP when:
-
You need low latency (no connection setup)
-
Occasional packet loss is acceptable
-
You’re building on a protocol that handles reliability itself
UDP Client
Send a datagram to a server and receive a response:
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::udp;
using asio::awaitable;
using asio::use_awaitable;
awaitable<void> udp_client()
{
auto executor = co_await asio::this_coro::executor;
// Create a socket
udp::socket socket(executor, udp::v4());
// Server address
udp::endpoint server(asio::ip::make_address("127.0.0.1"), 9000);
// Send a datagram
std::string message = "Hello, UDP!";
co_await socket.async_send_to(
asio::buffer(message), server, use_awaitable);
// Receive a response
char reply[1024];
udp::endpoint sender;
std::size_t n = co_await socket.async_receive_from(
asio::buffer(reply), sender, use_awaitable);
std::cout << "Received " << n << " bytes from " << sender << "\n";
std::cout << std::string_view(reply, n) << "\n";
}
int main()
{
asio::io_context ctx;
asio::co_spawn(ctx, udp_client(), asio::detached);
ctx.run();
}
UDP Server
Receive datagrams and echo them back:
awaitable<void> udp_server(unsigned short port)
{
auto executor = co_await asio::this_coro::executor;
// Create a socket bound to a port
udp::socket socket(executor, udp::endpoint(udp::v4(), port));
std::cout << "UDP server listening on port " << port << "\n";
char data[1024];
for (;;)
{
udp::endpoint sender;
std::size_t n = co_await socket.async_receive_from(
asio::buffer(data), sender, use_awaitable);
std::cout << "Received " << n << " bytes from " << sender << "\n";
// Echo it back
co_await socket.async_send_to(
asio::buffer(data, n), sender, use_awaitable);
}
}
int main()
{
asio::io_context ctx;
asio::co_spawn(ctx, udp_server(9000), asio::detached);
ctx.run();
}
Key Differences from TCP
Connected UDP
You can "connect" a UDP socket to a specific endpoint. This doesn’t establish a connection—it just sets a default destination:
awaitable<void> connected_udp()
{
auto executor = co_await asio::this_coro::executor;
udp::socket socket(executor, udp::v4());
udp::endpoint server(asio::ip::make_address("127.0.0.1"), 9000);
// Set default destination
co_await socket.async_connect(server, use_awaitable);
// Now you can use async_send instead of async_send_to
std::string message = "Hello!";
co_await socket.async_send(asio::buffer(message), use_awaitable);
// And async_receive instead of async_receive_from
char reply[1024];
std::size_t n = co_await socket.async_receive(
asio::buffer(reply), use_awaitable);
}
Benefits of connected UDP:
-
Slightly more efficient (no per-send address lookup)
-
Receives only packets from the connected address
-
Can detect ICMP errors (connection refused, etc.)
Multicast
To receive multicast traffic, join a multicast group:
awaitable<void> multicast_receiver()
{
auto executor = co_await asio::this_coro::executor;
// Listen on the multicast port
udp::socket socket(executor, udp::endpoint(udp::v4(), 30001));
// Join the multicast group
socket.set_option(asio::ip::multicast::join_group(
asio::ip::make_address("239.255.0.1")));
char data[1024];
for (;;)
{
udp::endpoint sender;
std::size_t n = co_await socket.async_receive_from(
asio::buffer(data), sender, use_awaitable);
std::cout << std::string_view(data, n) << "\n";
}
}
awaitable<void> multicast_sender()
{
auto executor = co_await asio::this_coro::executor;
udp::socket socket(executor, udp::v4());
udp::endpoint multicast_endpoint(
asio::ip::make_address("239.255.0.1"), 30001);
std::string message = "Hello, multicast!";
co_await socket.async_send_to(
asio::buffer(message), multicast_endpoint, use_awaitable);
}
Broadcast
To send to all hosts on the local network:
awaitable<void> broadcast_sender()
{
auto executor = co_await asio::this_coro::executor;
udp::socket socket(executor, udp::v4());
// Enable broadcast
socket.set_option(asio::socket_base::broadcast(true));
udp::endpoint broadcast_endpoint(
asio::ip::address_v4::broadcast(), 30001);
std::string message = "Hello, everyone!";
co_await socket.async_send_to(
asio::buffer(message), broadcast_endpoint, use_awaitable);
}
Error Handling
UDP operations can fail, but differently from TCP:
-
Send errors are rare (usually only if the network interface is down)
-
Receive errors can indicate the socket was closed or an ICMP error arrived
-
Lost packets are silent—there’s no error, the data just doesn’t arrive
For reliability, implement your own acknowledgment and retry logic, or use TCP.
Common Mistakes
Buffer too small — If the buffer is smaller than the incoming datagram, the excess is discarded. Make your buffer large enough.
Expecting reliability — UDP doesn’t guarantee delivery. Don’t assume every send results in a receive.
Blocking on receive — If no datagram arrives, async_receive_from waits
forever. Use a timeout if needed.
Next Steps
-
DNS Resolution — Look up hostnames
-
Recurring Timer — Add timeouts to UDP