TCP in depth: handshake, windows and congestion control
The three-way handshake and teardown, sequence and acknowledgement numbers, flow control windows, retransmission, slow start, and the latency consequences of each.
Opening and closing a connection
open: client --SYN seq=x--------> server
client <--SYN+ACK seq=y ack=x+1-- server
client --ACK ack=y+1---------> server
(one round trip before any data can be sent)
close: FIN -> ACK <- FIN -> ACK (four segments, or three with piggybacking)
the closer waits in TIME-WAIT for twice the maximum segment lifetime- The handshake costs one round trip before the first byte of a request, which is why connection reuse matters more than bandwidth on high-latency links.
- TIME-WAIT exists so delayed duplicate segments cannot be mistaken for a new connection. It also blocks that four-tuple briefly.
- TCP Fast Open and TLS session resumption shorten the cost of a new connection, but not always to zero.
- A full three-way handshake with a spoofed source is the basis of SYN flood attacks, which is what SYN cookies defend against.
Sequence numbers and retransmission
| Mechanism | Purpose | Observable effect |
|---|---|---|
| Sequence and ACK numbers | Ordering and delivery | Byte stream looks contiguous to the application |
| Cumulative ACK | Confirms everything up to a point | One lost segment stalls later ones |
| Selective ACK (SACK) | Confirms out-of-order ranges | Much faster recovery from loss |
| Duplicate ACK | Signals a gap | Three duplicate ACKs trigger fast retransmit |
| Retransmission timeout | Recovery when ACKs stop | Backs off exponentially; long stalls |
| Checksum | Detects corruption | Datagram dropped silently; TCP retransmits |
# observe retransmissions and window behaviour
ss -tni | head -20
# retrans:0/3 means three retransmits happened in this connection
# rtt:12.4/1.2 smoothed RTT and variance, in milliseconds
# cwnd:10 congestion window in segments
# send 1.3Mbps the delivery rate the kernel estimates
netstat -s | grep -A4 -i "^Tcp" # counters: retrans, bad segments, resetsFlow control and congestion control
Two different limits apply. Flow control protects the receiver's buffer with an advertised window; congestion control protects the network with a computed window. TCP sends the minimum of the two.
- Slow start doubles the congestion window per round trip until a threshold, so short transfers rarely reach full bandwidth.
- Congestion avoidance grows the window linearly afterwards, probing for more capacity.
- Loss detection halves the window in classic implementations; modern algorithms such as CUBIC and BBR behave differently.
- Bufferbloat appears when a large buffer absorbs the queue and latency rises before any loss occurs — a major reason BBR matters on last-mile links.
- Head-of-line blocking means one lost segment delays every later byte in the stream, which is what HTTP/2 over one TCP connection suffers from and QUIC avoids.
# inspect and tune the congestion algorithm on Linux
sysctl net.ipv4.tcp_congestion_control
sysctl net.ipv4.tcp_available_congestion_control
sysctl net.ipv4.tcp_rmem net.ipv4.tcp_wmem
# per-route: a long fat network may need a larger window
ip route show💡
Bandwidth-delay product decides whether a single connection can fill a link. On a 200 millisecond path with a 64 KB window, throughput caps near 2.6 Mbit/s no matter how much capacity exists. Window scaling and connection reuse are the fixes, not more bandwidth.
FAQ
Why are there four segments to close a connection?
TCP is full duplex, so each direction closes independently. The side that sends the second FIN waits in TIME-WAIT to drain any in-flight data.
Should I use TCP for everything?
For durable, ordered delivery yes. For real-time media and for protocols that want to make their own ordering decisions, UDP with an application-level scheme such as QUIC is often better.
Related
UDP, QUIC and choosing reliability Network debugging toolkit
Last refreshed 2026-09-18.