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

MechanismPurposeObservable effect
Sequence and ACK numbersOrdering and deliveryByte stream looks contiguous to the application
Cumulative ACKConfirms everything up to a pointOne lost segment stalls later ones
Selective ACK (SACK)Confirms out-of-order rangesMuch faster recovery from loss
Duplicate ACKSignals a gapThree duplicate ACKs trigger fast retransmit
Retransmission timeoutRecovery when ACKs stopBacks off exponentially; long stalls
ChecksumDetects corruptionDatagram 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, resets

Flow 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.

UDP, QUIC and choosing reliability Network debugging toolkit

Last refreshed 2026-09-18.