Lesson 17 / 18
UDP
Eight-byte header, message boundary preservation, applications suited to unreliable delivery, and building reliability at the application layer.
Contents
The previous two lessons established TCP’s guarantees and their cost: the handshake costs one round, retransmission on loss costs extra rounds, and ordered delivery brings waiting.
This cost is not necessary for every application; for some it is directly harmful. This lesson’s question is what a transport protocol that makes no guarantees at all is good for, and in which requirements it is the right choice.
Eight-Byte Header
The UDP (User Datagram Protocol) header is 8 bytes:
| Field | Size | Function |
|---|---|---|
| Source port | 2 bytes | Sending endpoint; zero if unused |
| Destination port | 2 bytes | Receiving endpoint |
| Length | 2 bytes | Datagram size including the header |
| Checksum | 2 bytes | Computed over the header, payload, and pseudo header |
Most of the fields in TCP’s 20-byte header are missing here: sequence number, acknowledgement number, window, flags. The reason for their absence is that the corresponding mechanisms are absent too.
The checksum field is optional in IPv4; if left at zero, no check is performed. It is mandatory in IPv6, because the header checksum has been removed at the network layer, leaving the only check at the transport layer.
Promises Not Made
| TCP’s promise | UDP |
|---|---|
| Delivery guarantee | None — a lost datagram is not resent |
| Order | None — datagrams can arrive in a different order than they were sent |
| No duplication | None — the same datagram can be delivered twice |
| Flow control | None — the receiver’s capacity is not taken into account |
| Congestion control | None — the network’s condition is not taken into account |
| Connection state | None — there is no handshake or closing |
This list should not be read as a list of shortcomings. Each row is also the removal of a cost: no state is kept, no round is waited out, and delayed data does not hold up the order.
Message Boundary
The only structural promise UDP makes is preserving the message boundary. Every datagram sent is delivered to the receiver in a single read operation, at the same size. TCP makes no such promise: the stream is an uninterrupted sequence of bytes, and the boundaries of the send calls are lost.
The following program sends the same three messages with both protocols and prints what is received.
import socket import threading import time MESSAGES = [b"ALFA", b"BRAVO", b"CHARLIE"] def tcp_server(received: list, ready: threading.Event) -> None: listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) listener.bind(("127.0.0.1", 9201)) listener.listen(1) ready.set() connection, _ = listener.accept() time.sleep(0.3) # all three sends pile up in the buffer while True: chunk = connection.recv(4096) if not chunk: break received.append(chunk) connection.close() listener.close() def udp_server(received: list, ready: threading.Event) -> None: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(("127.0.0.1", 9202)) ready.set() for _ in range(len(MESSAGES)): data, _ = sock.recvfrom(4096) received.append(data) sock.close() tcp_received: list = [] ready = threading.Event() threading.Thread(target=tcp_server, args=(tcp_received, ready), daemon=True).start() ready.wait() client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("127.0.0.1", 9201)) for m in MESSAGES: client.send(m) time.sleep(0.5) client.close() time.sleep(0.3) print("TCP sent :", MESSAGES) print("TCP received:", tcp_received) udp_received: list = [] ready2 = threading.Event() threading.Thread(target=udp_server, args=(udp_received, ready2), daemon=True).start() ready2.wait() udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for m in MESSAGES: udp_client.sendto(m, ("127.0.0.1", 9202)) time.sleep(0.5) udp_client.close() print("UDP sent :", MESSAGES) print("UDP received:", udp_received)
Output:
TCP sent : [b'ALFA', b'BRAVO', b'CHARLIE'] TCP received: [b'ALFABRAVOCHARLIE'] UDP sent : [b'ALFA', b'BRAVO', b'CHARLIE'] UDP received: [b'ALFA', b'BRAVO', b'CHARLIE']
On the TCP side, the three sends have merged into a single read. This merging is not guaranteed; depending on timing, it could just as well have arrived in two or three separate reads. What is guaranteed is that boundaries are not preserved: an application carrying messages over TCP must encode the message boundary itself — using a fixed length, a length prefix, or a delimiter.
On the UDP side, the three datagrams arrived in three separate reads, and this is guaranteed. If the receiver’s read buffer is smaller than the datagram, the datagram is truncated; the remaining part is lost.
When to Choose
The selection criterion is what the application does with delayed data.
| Application class | Choice | Rationale |
|---|---|---|
| File transfer | TCP | Every byte is required; order and integrity are mandatory |
| Name resolution | UDP | One request, one response; the handshake round would be most of the cost |
| Address leasing | UDP | A connection cannot be established without an address |
| Real-time audio and video | UDP | A delayed frame is worthless; retransmission does harm |
| Interactive game state | UDP | Stale position data is invalidated by newer data |
| Continuous measurement reporting | UDP | The loss of a single sample is negligible |
| Broadcast and multicast | UDP | TCP does not support multiple recipients |
The real-time streaming row shows the essence of the choice. If an audio frame arrives 200 ms late, the moment it was meant to be played has already passed; requesting it again only burdens the network further. Putting silence or an estimate in place of a lost frame is better than waiting for the delayed one.
Head-of-Line Blocking
TCP’s ordered-delivery promise carries a second cost. The receiver cannot hand later data to the application until it fills a gap in the stream — even if it already has that data. When a segment is lost, the segments that arrive after it are held in the buffer. This is called head-of-line blocking.
On a connection carrying a single stream, this is the correct behavior. But if a single TCP connection carries multiple independent logical streams, loss in one stream holds up the others too — even though there is no dependency between them.
UDP has no such blocking, because it makes no ordering promise either. This is one of the reasons protocols that multiplex independent streams are built on top of UDP: order and reliability are managed per stream, and streams do not hold each other up.
Building Reliability at the Application Layer
Choosing UDP does not mean giving up on reliability; it means building only as much of it as needed. The pieces commonly built at the application layer are:
- Retry and timeout. The typical pattern in name resolution: if no response arrives, the same query is repeated, and after a few attempts another server is tried.
- Sequence number. Numbering datagrams makes loss and reordering detectable; the application decides which gap to make up for.
- Selective retransmission. Only data that is still valuable is requested again.
- Rate control. Since there is no congestion control, an application sending continuous data must impose its own rate limit.
The last item must not be neglected. An uncontrolled UDP flow forces the TCP flows sharing the same path to back off and unfairly grows its own share; it also creates a congestion risk for the network as a whole.
If all of these pieces are built, it looks like rewriting TCP. The difference is which pieces get built: the application knows which part of its own data becomes worthless when delayed; TCP cannot know that.
Size and Fragmentation
UDP does not split the body. If the application sends a 3000-byte datagram, the splitting work falls to the IP layer, and the fragmentation cost from the Frame, Packet, Segment lesson arises: if one of the fragments is lost, the entire datagram is lost.
For this reason, protocols that use UDP keep the datagram size under the path MTU. For Ethernet and IPv4 the limit is bytes; choosing a safer threshold against the possibility of a tunnel being present is common practice.
Source Address Spoofing
Since UDP has no handshake, the sender’s address is never verified at any stage. A datagram whose source address is made to appear as belonging to someone else causes the response to be sent to that address.
The scale problem this creates appears in protocols where the response is larger than the request: a small request directs a large response at the target, and the traffic is reflected with amplification. The countermeasures lie outside the protocol:
- Source address validation. The network operator checks that the source addresses of packets leaving its network belong to that network. A packet with a spoofed source cannot leave the network.
- Response rate limiting. The number of responses given to the same source is limited.
- Query validation. Before giving a large response, the server tests with a small exchange whether the request really came from that address.
The same spoofing is harder in TCP: seeing the sequence number in the second step of the handshake is required, and the number is chosen at random. This is one of the reasons for the randomness requirement noted in the TCP lesson.
Summary
- The UDP header is 8 bytes; it has no sequence, acknowledgement, window, or flag fields, because the corresponding mechanisms are absent too.
- Delivery, order, no-duplication, flow-control, and congestion-control guarantees are not made; in exchange, no state is kept and no round is waited out.
- UDP preserves the message boundary; TCP does not — an application carrying messages over TCP must encode the boundary itself.
- The selection criterion is whether delayed data is still valuable to the application.
- Head-of-line blocking causes independent streams to hold each other up on a single TCP connection; UDP has no such blocking.
- Retry, sequence numbering, and rate control can be built at the application layer; neglecting rate control disrupts flow sharing.
Next Step
The throughput of both protocols runs into the same physical realities: putting a bit on the wire takes time, light travels at a finite speed, queues hold packets up. The next lesson separates these components to show, by calculation, where delay comes from, why the product of bandwidth and delay is a measure of capacity, and why what determines a transfer’s duration is most often not bandwidth.
To keep your progress and take notes, Log in
My notes
Log in to take notes.