---
title: UDP
source: 'https://academia.sh/en/courses/network-models/udp'
course: 'Network Models and Protocols'
language: en
updated: '2026-08-17T18:07:14+00:00'
license: 'CC BY-SA 4.0'
---

# UDP

Eight-byte header, message boundary preservation, applications suited to unreliable delivery, and building reliability at the application layer.

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.

```python
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 $1500 - 20 - 8 = 1472$ 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.
