You are on a video call. Someone's face freezes for a quarter-second, a syllable turns to static, and then — without anyone asking — the picture snaps back to the present moment. Nobody rewound. The lost frame was simply abandoned: by the time it could have been re-sent, it was already stale, and a fresh frame had taken its place. That "throw it away and move on" behaviour is the whole personality of the protocol underneath: UDP, the User Datagram Protocol, and the sockets you use to speak it.
You have already met the SOCK_DGRAM), backed by
Recall the seven-call TCP dance: socket → bind → listen → accept on the server,
socket → connect on the client, then read/write, then
close. UDP throws most of that away. There is no connection, so there is
nothing to establish and nothing to accept. A UDP program calls socket(), optionally
bind()s a port (a server must; a client usually needn't), and then simply
sends and receives datagrams — each one carrying its destination (or source) address
with it, packet by packet.
The two workhorse calls are sendto() and recvfrom():
sendto(sock, bytes, dest_addr) — fire one datagram at
dest_addr. There is no prior connect(), so you name the destination on
every send. The call returns as soon as the datagram is handed to the kernel; it does
not wait for, or even learn of, delivery.
recvfrom(sock) → (bytes, src_addr) — receive exactly one
datagram, and learn who sent it. That returned src_addr is how a connectionless
server knows where to reply — it did not accept() a per-client socket, so it must read
the sender's address off each packet.
Because there is no connection, one UDP socket can converse with thousands of peers at once
through a single file descriptor — every datagram is self-addressed. There is no accept()
spawning a socket per client, and often no per-client state in the kernel at all. This is exactly why
DNS resolvers, which field a torrent of tiny independent queries from everywhere, live
on UDP.
recvfrom() returns one whole datagram, exactly as it was sent.
Put the TCP and UDP server loops next to each other and the missing machinery jumps out. UDP has no
listen(), no accept(), no handshake, and no per-connection socket — just a
bound port and a recvfrom/sendto loop.
Here is a UDP echo server and client as illustrative (non-runnable) code — real datagrams need a real network, which the sandbox does not have. Notice the address travelling with each call:
The single most painful bug in TCP programming is that write("hello") then
write("world") can arrive as helloworld, or hel then
loworld — TCP is a faucet of bytes and does not remember where one write ended.
UDP is the exact opposite. One sendto becomes one datagram becomes one
recvfrom. The message you sent is the message that comes out, whole, or it does
not come out at all — never smeared into its neighbours.
The demo below models both protocols over the same three logical messages, so you can watch the boundaries dissolve under TCP and hold under UDP.
Read that contrast carefully. On TCP you must add your own
UDP hands you message boundaries and then walks away. The network is free to lose your datagram, duplicate it, or reorder it relative to its siblings, and UDP will never tell you. If your application needs any of the guarantees TCP gave away, you must rebuild them yourself — on top of UDP.
This is why the modern web's fast transport, QUIC (the basis of HTTP/3), is built on UDP: it wanted to design its own reliability, ordering, and congestion control from scratch — without waiting for a decade of TCP to change in every operating-system kernel — so it started from the blank slate UDP provides.
Two traps snare almost everyone the first time. First, you own reliability. UDP will
silently lose, reorder and duplicate datagrams and never say a word; a client that does a single
recvfrom() and waits for a reply that was dropped will block forever. If your
data must arrive, you must add sequence numbers, acknowledgements, timeouts and de-duplication —
i.e. you must build the reliability TCP would have given you for free.
Second, size matters. A datagram is one packet's worth of payload. Send one
larger than the path's
The decision is really about what lateness costs you. TCP guarantees delivery, but the price of that guarantee is waiting: a lost segment stalls everything behind it until it is re-sent (head-of-line blocking). For some data, a late byte is worse than a missing one.
UDP's header is a mere 8 bytes: source port, destination port, length, and a checksum. That checksum lets the receiver detect a corrupted datagram (and silently drop it) — but there is no field for a sequence number, an acknowledgement, or a window, because UDP has no notion of ordering, delivery or flow. The design philosophy, straight from its 1980 spec (RFC 768), is "a thin shim over IP that adds just ports and error-detection, and gets out of the way." Everything richer is left to the application — which is precisely the freedom that lets QUIC, DNS and your favourite game all live on the same eight-byte header.