Guide contents

Lesson 8 of 129

TCP and UDP

The transport layer moves data between processes on two hosts, identified by IP address and port. Two protocols dominate: TCP (Transmission Control Protocol) provides a reliable, ordered byte stream, and UDP (User Datagram Protocol) provides unreliable, independent messages. Higher-level protocols such as HTTP, and system design choices such as connection pooling, depend on their behavior.

TCP overview

TCP is connection-oriented: two endpoints establish state before exchanging data. It gives the application:

  • Reliable delivery: lost segments are retransmitted.
  • Ordered delivery: bytes arrive in the order sent.
  • Byte-stream abstraction: no message boundaries; the application must frame its own messages.
  • Flow control: the sender does not overwhelm the receiver.
  • Congestion control: the sender slows down when the network is overloaded.

Connection establishment: three-way handshake

sequenceDiagram
    participant C as Client
    participant S as Server
    C->>S: SYN (seq = x)
    S->>C: SYN-ACK (seq = y, ack = x+1)
    C->>S: ACK (ack = y+1)
    Note over C,S: Connection established, data can flow
  1. The client sends SYN with an initial sequence number.
  2. The server replies SYN-ACK with its own initial sequence number and acknowledges the client's.
  3. The client sends ACK. The client can send data with this segment.

A connection is identified by the 4-tuple of source IP, source port, destination IP, and destination port. The handshake costs one round trip (RTT) before the first data byte. With TLS on top, additional round trips are needed (see HTTP/1.1, HTTP/2, and HTTP/3).

Connection teardown

Each direction is closed independently with a FIN segment acknowledged by the peer. The side that closes first enters the TIME_WAIT state for a period (commonly on the order of a minute) so that delayed segments of the old connection are not mistaken for a new one. Servers or clients that open and close very many short connections can accumulate large numbers of sockets in TIME_WAIT and exhaust ephemeral ports.

A server keeps half-open connections (SYN received, handshake incomplete) in a bounded queue. A flood of SYN segments that are never completed can fill this queue and block legitimate clients (a SYN flood). Mitigations include SYN cookies, which encode connection state in the sequence number so no queue entry is needed.

A RST segment aborts a connection immediately, for example when data arrives for a closed port.

Reliability

  • Each byte has a sequence number. The receiver returns cumulative acknowledgements naming the next byte it expects.
  • The sender keeps a retransmission timer based on measured round-trip time. If no acknowledgement arrives, the segment is resent.
  • Fast retransmit: three duplicate acknowledgements for the same byte signal a likely loss, and the sender retransmits without waiting for the timer.
  • Selective acknowledgement (SACK), when negotiated, lets the receiver report non-contiguous ranges it has received, so only missing segments are resent.
  • A checksum detects corrupted segments, which are dropped.

Flow control

Flow control protects the receiver's buffer. The receiver advertises a receive window: how many bytes it can accept beyond the last acknowledged byte. The sender never has more unacknowledged data in flight than this window. If the application reads slowly, the window shrinks, and it can reach zero, pausing the sender until the receiver advertises space again.

Congestion control

Congestion control protects the network. The sender maintains a congestion window (cwnd), and the amount in flight is limited by the minimum of cwnd and the receive window.

Classic algorithm phases:

  • Slow start: cwnd starts small (commonly around 10 segments in modern stacks) and grows roughly exponentially, doubling each RTT, until it reaches the slow-start threshold (ssthresh) or a loss occurs. With an initial cwnd of 10 segments, the sender can have 10, 20, 40, and 80 segments in flight during successive round trips.
  • Congestion avoidance: cwnd grows by about one segment per RTT (additive increase).
  • On loss: cwnd is cut, typically halved for loss detected by duplicate acknowledgements (multiplicative decrease). If the retransmission timer expires instead, the loss signal is stronger and cwnd is reset to a small value and slow start restarts. This additive-increase, multiplicative-decrease pattern converges to a fair share among competing flows.

Different algorithms exist: Reno and CUBIC are loss-based, and BBR estimates bandwidth and round-trip time instead of reacting to loss alone.

The receive window and cwnd together bound throughput: at most one window of data can be sent per round trip. With a 64 KB window and a 100 ms RTT, throughput is at most 64 KB / 0.1 s = 640 KB/s (about 5 Mbit/s), regardless of link capacity. The window needed to fill a link is the bandwidth-delay product: a 1 Gbit/s link with 100 ms RTT needs about 125 MB/s x 0.1 s = 12.5 MB in flight. Windows larger than 64 KB require the window scaling option negotiated in the handshake.

Consequences for system design: a new connection starts slow, so short-lived connections rarely reach full throughput. Reusing established connections avoids repeated slow start and handshake cost.

Head-of-line blocking

TCP delivers bytes in order. If one segment is lost, later segments that already arrived are held in the receiver's buffer until the missing one is retransmitted. This is head-of-line (HOL) blocking at the transport layer.

It matters when several independent logical streams share one TCP connection. HTTP/2 multiplexes many requests over one TCP connection, so one lost packet stalls all of them. HTTP/3 addresses this by running over QUIC on UDP, where each stream is delivered independently.

UDP

UDP adds only port numbers, a length, and a checksum on top of IP.

  • Connectionless: no handshake, no state; each datagram stands alone.
  • No guarantees: datagrams can be lost, duplicated, or reordered.
  • Message-oriented: one send produces one datagram, and boundaries are preserved.
  • No flow or congestion control: the application must avoid flooding the network.
  • Low overhead: 8-byte header, and no connection setup delay.
  • Supports broadcast and multicast in ways TCP cannot.

TCP versus UDP

Property TCP UDP
Connection Handshake required None
Delivery Reliable, ordered Best effort
Abstraction Byte stream Datagrams
Flow and congestion control Yes No (application's job)
Header size 20 bytes minimum 8 bytes
Typical uses HTTP/1.1, HTTP/2, databases, SSH, SMTP DNS queries, video and voice, games, QUIC, service discovery

When to use UDP

  • Latency matters more than completeness: live audio and video, online games. A late packet is useless, so retransmission is worse than skipping it.
  • Small request/response exchanges: DNS queries fit in one datagram each way and avoid connection setup.
  • Custom transport semantics: QUIC builds reliability, multiplexing, and congestion control in user space over UDP, allowing per-stream delivery and faster evolution than kernel TCP.
  • Multicast or broadcast discovery and telemetry.

When using UDP for anything that needs reliability, the application must add its own acknowledgements, retransmission, ordering, and congestion handling.

Connection pooling

Opening a TCP connection costs a handshake round trip, TLS setup if used, and slow start. A connection pool keeps a set of established connections open and hands them to callers.

  • Database drivers, HTTP clients, and service-to-service clients use pools.
  • A pool has a maximum size (to protect the server, which has limits on connections and per-connection memory), and an idle timeout that closes unused connections.
  • Servers or intermediaries may close idle connections; the client must detect a closed connection and retry on a fresh one, or validate connections before use.
  • HTTP keep-alive is the same idea applied to HTTP: one connection carries several requests.
  • Proxies such as PgBouncer multiplex many application connections over fewer database connections, because each database connection is costly.
flowchart LR
    A["App threads"] --> P["Connection pool (max N)"]
    P --> C1["Conn 1"]
    P --> C2["Conn 2"]
    P --> C3["Conn N"]
    C1 --> DB["Server"]
    C2 --> DB
    C3 --> DB

Sizing trade-off: too few connections serialize requests and add queueing delay; too many exhaust server resources and can worsen throughput.

Interview questions

  • Why does TCP need a three-way handshake? Both sides must agree on initial sequence numbers and confirm that the other can send and receive. Two messages would leave the server unable to confirm that the client received its sequence number, and the handshake also lets the server reject delayed duplicate SYN segments from old connections.
  • What is the difference between flow control and congestion control? Flow control limits the sender to the receiver's advertised window. Congestion control limits it to the congestion window, inferred from network conditions such as loss or delay.
  • Why is UDP used for DNS and video calls? DNS queries are single small exchanges where a handshake would dominate cost. Real-time media tolerates loss but not the delay of retransmission and in-order delivery.
  • What is head-of-line blocking in TCP? A lost segment blocks delivery of all later bytes on that connection until it is retransmitted, even for independent streams multiplexed on it.