Networking with the Standard Library
UDP Sockets
A UdpSocket (std::net::UdpSocket) is a connectionless datagram socket that sends and receives discrete, message-bounded packets over the User Datagram Protocol (UDP). Unlike TcpStream, a single UdpSocket can exchange packets with any number of remote peers without establishing a connection handshake.
Peer A (127.0.0.1:50001) -- send_to("ping") --> +----------------------------+
| UdpSocket |
Peer B (127.0.0.1:50002) -- send_to("stat") --> | bound to 127.0.0.1:9000 |
| recv_from() -> (len, peer) |
Peer A (127.0.0.1:50001) <- send_to("pong") --- +----------------------------+
Unconnected vs Connected UdpSocket Methods
A UdpSocket is always created via UdpSocket::bind(addr). Once bound, it can operate in two modes:
- Unconnected (Ad-hoc Addressing): Uses
send_to(buf, target)andrecv_from(&mut buf)to specify or inspect the peer address on every packet. - Connected (Default Peer Filter): Calling
socket.connect(remote_addr)does not perform a network handshake; instead, it configures the OS kernel to send all.send(buf)datagrams toremote_addrand filter.recv(&mut buf)so only datagrams arriving fromremote_addrare accepted.
| Method | Signature | Description |
|---|---|---|
UdpSocket::bind(addr) |
bind<A: ToSocketAddrs>(addr: A) -> io::Result<UdpSocket> |
Binds a new UDP socket to a local address and port. |
.send_to(buf, addr) |
send_to<A: ToSocketAddrs>(&self, buf: &[u8], addr: A) -> io::Result<usize> |
Sends a single datagram to addr. |
.recv_from(&mut buf) |
recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> |
Reads a single datagram into buf, returning (byte_count, sender_addr). |
.peek_from(&mut buf) |
peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> |
Inspects the next datagram and sender without removing it from the receive queue. |
.connect(addr) |
connect<A: ToSocketAddrs>(&self, addr: A) -> io::Result<()> |
Sets the default remote peer address for send and recv. |
.send(buf) / .recv(&mut buf) |
send(&self, buf: &[u8]) -> io::Result<usize> |
Sends or receives datagrams to/from the connected peer. |
Exchanging Datagrams Between Two UDP Sockets
use std::io;
use std::net::UdpSocket;
fn main() -> io::Result<()> {
let server = UdpSocket::bind("127.0.0.1:0")?;
let client = UdpSocket::bind("127.0.0.1:0")?;
let server_addr = server.local_addr()?;
let client_addr = client.local_addr()?;
// Client connects to `server_addr` so it can use `.send()` and `.recv()`
client.connect(server_addr)?;
client.send(b"telemetry:cpu=42")?;
// Server receives the datagram and the sender's SocketAddr via `.recv_from()`
let mut buf = [0u8; 128];
let (n, sender) = server.recv_from(&mut buf)?;
assert_eq!(sender, client_addr);
let msg = std::str::from_utf8(&buf[..n]).unwrap();
println!("Server received ({n} bytes): {msg}");
// Server replies directly to `sender`
server.send_to(b"ACK:42", sender)?;
let n_reply = client.recv(&mut buf)?;
println!(
"Client received reply: {}",
std::str::from_utf8(&buf[..n_reply]).unwrap()
);
Ok(())
}
Server received (16 bytes): telemetry:cpu=42
Client received reply: ACK:42
Datagram Boundaries and Truncation Hazard
Unlike TCP (where unread bytes remain in the stream for the next read() call), each recv or recv_from call consumes an entire UDP datagram:
- If a datagram is 500 bytes long and you pass a 64-byte buffer (
[0u8; 64]), the first 64 bytes are copied into the buffer and the remaining 436 bytes of that datagram are permanently discarded (or on Windows,WSAEMSGSIZEis returned). - For general internet UDP protocols, keep datagram payloads under
1,400bytes (to avoid IP-layer fragmentation below the standard1,500-byte Ethernet MTU) and allocate a receive buffer of at least1,500–65,535bytes.