Guide

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:

  1. The client sends SYN with a random initial sequence number, say seq=1000.
  2. The server replies SYN-ACK with its own seq=5000 and ack=1001. That means "I have everything up to 1000, send 1001 next".
  3. 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.