TCP Connection Management
The Three-Way Handshake (Establishment)
TCP uses a Three-Way Handshake to synchronize state and ensure both sides are capable of bidirectional communication before any real data is sent.
Client Server
│ │
│ SYN=1, seq=x │
│ ─────────────────────────────────────────▶ │ ① SYN (Client to Server)
│ "I want to connect. My start is x." │ State: SYN_SENT
│ │
│ SYN=1, ACK=1, seq=y, ack=x+1 │
│ ◀───────────────────────────────────────── │ ② SYN-ACK (Server Response)
│ "Received. My start is y. Confirmed x." │ State: SYN_RCVD
│ │
│ ACK=1, seq=x+1, ack=y+1 │
│ ─────────────────────────────────────────▶ │ ③ ACK (Acknowledgment)
│ "Received. Let's go." │ State: ESTABLISHED
│ │ State: ESTABLISHED
Why 3 steps and not 2?
To prevent stale connection requests from wasting resources. Imagine a client sends a request (SYN), but it gets stuck in a network loop. The client times out and sends a second SYN which connects, transfers data, and closes. Suddenly, the first "stale" SYN arrives at the server.
- If only 2 steps: The server would open a connection immediately, but the client would ignore it. The server wastes a port and memory.
- With 3 steps: The server sends a SYN-ACK, but the client (seeing an old acknowledgement) won't send the final ACK. The connection never opens.
The Four-Way Handshake (Termination)
Because TCP is Full-Duplex, each side must close its transmission independently.
Client Server
│ │
│ FIN=1, seq=u │
│ ─────────────────────────────────────────▶ │ ① Client FIN
│ "I'm done sending data." │ FIN_WAIT_1
│ │
│ ACK=1, ack=u+1 │
│ ◀───────────────────────────────────────── │ ② Server ACK
│ "Received. Give me a moment." │ CLOSE_WAIT
│ FIN_WAIT_2 │
│ │
│ ... Server flushes its buffer ... │
│ │
│ FIN=1, seq=w │
│ ◀───────────────────────────────────────── │ ③ Server FIN
│ "I'm also done. Closing." │ LAST_ACK
│ │
│ ACK=1, ack=w+1 │
│ ─────────────────────────────────────────▶ │ ④ Final ACK
│ TIME_WAIT (Wait 2MSL) │ CLOSED
│ → CLOSED │
The TIME_WAIT State
After sending the final ACK, the active-closing side (usually the client) enters TIME_WAIT for 2MSL (Maximum Segment Lifetime).
Why stay open?
- Last ACK Reliability: If the final ACK is lost, the server will re-send its FIN. The client must be alive to re-send the ACK.
- Lingering Packet Clearance: We must wait long enough to ensure any stray packets from the old connection have died in the network. This prevents "old" data from being interpreted as "new" data in a subsequent connection using the same ports.
Engineering Insights
The SYN Flood Attack
During the handshake, a malicious actor can send thousands of SYNs but never respond to the SYN-ACKs (leaving connections in SYN_RCVD). This fills up the server's "Backlog Queue," preventing legitimate users from connecting. Modern kernels mitigate this using SYN Cookies, where connection state is stored in the Sequence Number itself rather than in RAM.
Avoiding TIME_WAIT Build-up
In high-concurrency microservices (Short-lived connections), servers can run out of ephemeral ports because thousands of connections are stuck in TIME_WAIT. Strategies include:
- Connection Pooling: Reusing established connections.
SO_REUSEADDR: Allowing the OS to re-bind a port even if it's inTIME_WAIT.- Kernel Tuning: Adjusting
tcp_tw_reuseandtcp_max_tw_bucketsin/etc/sysctl.conf.