Read alongside
Computer Networks: A Systems Approach: 5.2 Reliable Byte Stream (TCP)
Main reading: segment format, connection setup and teardown, sliding window, adaptive retransmission and record boundaries.
Objectives
- Explain TCP byte-stream semantics and implement length-prefixed framing that handles partial and coalesced reads.
- Draw the three-way handshake, the teardown and half-close, naming each state including CLOSE-WAIT and TIME-WAIT.
- Explain why TIME-WAIT exists, which side enters it, and how connection churn leads to ephemeral-port exhaustion.
- Explain cumulative ACKs, SACK, RTO estimation with backoff and fast retransmit, and why one loss stalls in-order delivery.
- Explain why application-level acknowledgments and idempotent retries are needed even though TCP is reliable.
- Choose between TCP and UDP for a given application and list what a UDP-based protocol must add itself.
Why it matters
Almost every service you build speaks TCP, directly or under HTTP, gRPC or a database driver. Bugs that look like application problems (truncated messages, requests that hang after a peer crashes, port exhaustion under load, duplicate side effects after a retry) come from misunderstanding what TCP guarantees. UDP matters because DNS, QUIC, media and games use it and must supply whatever reliability they need.
Core ideas
Byte stream, not messages. TCP delivers bytes in order without gaps or duplicates, but it does not preserve the boundaries of your send calls: one send may arrive in several reads and two sends may arrive in one. Applications therefore frame messages with a length prefix, a delimiter, or a self-describing format such as HTTP/1.1 headers plus Content-Length. UDP preserves datagram boundaries but may drop, duplicate or reorder datagrams.
Connection lifecycle. The three-way handshake (SYN, SYN-ACK, ACK) synchronizes initial sequence numbers in both directions and costs one round trip before data flows. Teardown is per direction: each side sends FIN when it has finished sending, so a connection can be half-closed. The side that closes first ends in TIME-WAIT for twice the maximum segment lifetime, so that a lost final ACK can be answered and delayed segments from the old connection cannot be mistaken for a new connection with the same four-tuple. A client that opens and closes many short connections to one server accumulates TIME-WAIT sockets and can run out of ephemeral ports; reusing connections is the usual fix. RST aborts immediately, for example when a segment arrives for a port with no listener.
Reliability machinery. Every byte has a sequence number and the receiver returns cumulative acknowledgments, plus selective acknowledgments (SACK) when both sides support them. The sender retransmits after a timeout computed from a smoothed RTT and its variance, doubling the timeout on repeated losses. Fast retransmit resends a segment after several duplicate ACKs reveal a hole, without waiting for the timer. Out-of-order data is buffered until the gap is filled, so one lost segment holds back everything behind it.
What an ACK means. A TCP acknowledgment says the peer's kernel received the bytes. It does not say the application read them, processed them or stored them durably. If a server crashes after its kernel acknowledged a request, the client saw transport-level success and gets no response. That is why protocols carry application-level acknowledgments (a response, a commit reply) and why retries need idempotency keys: after a timeout the client cannot know whether the request was executed.
UDP. Ports for demultiplexing and a checksum, nothing else: no connection state, retransmission, ordering or congestion control. Protocols built on UDP must add what they need, including congestion control when they send at volume.
client server
SYN seq=x ------>
<------ SYN-ACK seq=y ack=x+1
ACK ack=y+1 ------> (data flows both ways)
FIN ------> (server may still send: half-close)
<------ ACK
<------ FIN
ACK ------> client waits in TIME-WAIT (2 x MSL)
Common misconceptions
- "Each send arrives as one read." TCP has no message boundaries; readers must loop until a complete frame has been assembled.
- "An ACK means the server processed my request." It means the peer's kernel has the bytes; only an application-level response confirms processing.
- "TIME-WAIT is a bug to switch off." It protects new connections from stale segments; reduce churn by reusing connections rather than shortening it blindly.
- "TCP notices a dead peer quickly." With no data in flight, a silently dead peer is detected only by keepalives (off by default, and slow when enabled), an application heartbeat or a timeout on the next exchange.
- "UDP is faster than TCP." UDP does less for you; performance depends on what the application rebuilds on top and on how networks treat UDP traffic.
How to approach it
Be able to draw the handshake and teardown with state names (LISTEN, SYN-RECEIVED, ESTABLISHED, FIN-WAIT-1 and -2, CLOSE-WAIT, LAST-ACK, TIME-WAIT) and explain each transition. Practice explaining framing with code. Mid and senior follow-ups include retransmission timing, thousands of CLOSE-WAIT sockets (the local application never called close), port exhaustion, idempotent retries, and when you would choose UDP plus your own reliability.
Self-check before you finish
- What happens if the final ACK of the handshake is lost?
- A server shows thousands of sockets in CLOSE-WAIT. Whose bug is it, and what is it?
- What problem does TIME-WAIT prevent, and why does it last two maximum segment lifetimes?
- How does a sender detect a loss without waiting for the retransmission timer?
- A client times out waiting for a payment response. Can it retry safely, and what does it need?
- Which guarantees does UDP give, and which ones must DNS or QUIC add?
- Why does one lost segment delay bytes that have already arrived?