Networking
TCP handshake and reliability
How TCP sets up a connection with a three-way handshake, numbers every byte, recovers lost packets, and closes cleanly, along with the costs each step adds.
IP delivers packets on a best-effort basis. They can be dropped, duplicated, reordered or corrupted. TCP turns that into a reliable, ordered stream of bytes between two ports. It does this with a handshake, sequence numbers, acknowledgments and retransmission.
Concept
Before you start: a sequence number is the position of a byte in the stream. An ACK is the receiver's message saying "I have everything before this number". Both are covered in the handshake below.
Walking through it: the three-way handshake
Before any data flows, both sides agree on starting sequence numbers:
- The client sends SYN with a random initial sequence number, say
seq=1000. - The server replies SYN-ACK with its own
seq=5000andack=1001. That means "I have everything up to 1000, send 1001 next". - The client sends ACK with
ack=5001. It can attach the first data in the same packet.
The cost to remember is one full round trip before the client can send its request. Inside a data center, with a 2 ms link, that is nothing. Between Mumbai and Virginia, at about 200 ms RTT, it is 200 ms of waiting. TLS adds more on top (see TLS). This is the biggest reason connection reuse matters.
Sequence numbers, ACKs and retransmission
Every byte has a sequence number. The receiver sends cumulative ACKs that say "I have everything before byte N". If the sender sees no ACK before its retransmission timeout (RTO), it resends. The RTO comes from measured RTT. On Linux it is never below 200 ms, and the initial value is 1 second.
Waiting for a timeout is slow, so TCP has a faster path. Fast retransmit resends a packet after three duplicate ACKs. Those mean a later packet arrived but an earlier one is missing. Modern stacks add SACK (selective acknowledgment), so the receiver can name exactly which ranges it holds.
Flow control: the receive window
The receiver advertises a receive window (rwnd) in every ACK. It says how many more bytes it has buffer space for. The sender never has more unacknowledged bytes in flight than that. The original header field is 16 bits, which caps the window at 65,535 bytes. The window scale option, negotiated in the SYN, multiplies it, allowing windows of many megabytes.
Flow control protects the receiver. Protecting the network is the job of congestion control, covered in congestion control and BDP.
Teardown and TIME_WAIT
Closing takes four steps. Each side sends a FIN, and the other side ACKs it. The side that closes first enters TIME_WAIT for twice the maximum segment lifetime, 60 seconds on Linux. That keeps stray packets from the old connection from being mistaken for a new one on the same port pair.
TIME_WAIT is harmless in moderation. But a client that opens and closes thousands of connections per second to one server can build up tens of thousands of TIME_WAIT sockets. It can then run out of ephemeral ports, which Linux uses from roughly 28,000 by default (32768 to 60999). The fix is almost never kernel tuning. It is connection pooling, so you stop paying the handshake and teardown on every request.
Common mistakes
- Assuming TCP is free to set up. Each new connection costs at least one round trip before any data.
- Trusting the default timeouts. A connection to a dead host can hang for minutes unless you set a connect timeout.
- Tuning kernel settings for TIME_WAIT first. Pool connections instead.
Check yourself
- Why is the first byte of a response at least 2 RTTs away on a new HTTP over TCP connection? One round trip for the handshake, and one for the request and its response.
- What triggers a fast retransmit? Three duplicate ACKs, which show that a later packet arrived while an earlier one is missing.
Key takeaways
- The three-way handshake costs one round trip before any request. Its SYN and SYN-ACK exchange sets the sequence numbers.
- Every byte is numbered, acknowledged cumulatively, and resent after a timeout or three duplicate ACKs.
- The receive window protects the receiver. Window scaling lets it grow beyond 65 KB.
- TIME_WAIT is a consequence of closing, and the real fix for buildup is connection pooling.
Further learning
Next, see how the congestion window makes new connections slow, in congestion control and BDP.
For the TLS handshake that runs after this one, see TLS.
Related
Packets, layers and addresses
How data is wrapped in layered headers, what the MTU and MSS limit, and how IPv4 addresses, CIDR blocks, private ranges and NAT decide what can reach what.
Congestion control and BDP
Why a new TCP connection starts slow, how slow start works with a 100 KB example, how CUBIC and BBR differ, and how the bandwidth-delay product tells you how much data must be in flight.
Networking: IP, TCP and UDP
Packets, addresses, ports, and the handshakes and congestion control that decide how fast your bytes arrive. Explains why a new connection is slow and how to check it on the wire.