HomeInternet & NetworksTCP/IP Handshake & Congestion Control

🤝 TCP/IP Handshake & Congestion Control

Watch the real TCP three-way handshake (SYN → SYN-ACK → ACK) with genuine sequence numbers, then a live AIMD congestion-window simulation: exponential slow start, linear congestion avoidance, and multiplicative decrease on packet loss.

Internet & Networks2DAdvanced60 FPS
tcp-ip-handshake ↗ Open standalone

About the TCP Handshake and Congestion Control

Every TCP connection begins with a three-way handshake. The client picks a random 32-bit initial sequence number (ISN) and sends a segment with the SYN flag set. The server replies with its own random ISN and the ACK flag set, acknowledging the client's sequence number by echoing ISN+1 in its acknowledgment field — a single segment doing two jobs, hence "SYN-ACK." The client finishes by sending an ACK that acknowledges the server's ISN+1. Only then is the connection ESTABLISHED on both ends, and sequence numbers from this point track exactly how many bytes each side has sent.

Once data starts flowing, TCP has to decide how fast to send without overwhelming the network — the job of congestion control. Van Jacobson's algorithm, dating to 1988 and still the basis of modern TCP, starts cautiously: the congestion window (cwnd) begins at one segment and doubles every round-trip time during "slow start," growing exponentially. Once cwnd reaches a threshold (ssthresh), TCP switches to "congestion avoidance," adding just one segment per RTT — additive increase. When a loss is detected, TCP treats it as a signal of congestion: ssthresh is set to half the current cwnd, cwnd collapses to a small value, and the exponential climb restarts. This additive-increase/multiplicative-decrease (AIMD) pattern is what produces TCP's characteristic sawtooth throughput graph, and it is also what keeps many independent TCP flows sharing a link roughly fairly.

Because cwnd effectively caps how many unacknowledged segments can be in flight, throughput is bounded by roughly cwnd·MSS / RTT — high RTT or frequent loss both suppress it. The well-known Mathis formula approximates steady-state TCP throughput as inversely proportional to RTT and to the square root of the loss rate; it is only an approximation (it ignores timeouts, slow start and other effects), but it captures the same intuition this simulation lets you explore directly: raise the loss slider or the RTT slider and watch the achievable throughput fall.

Frequently Asked Questions

Why does TCP need three messages just to start a connection?

Both sides need to agree on, and acknowledge, each other's starting sequence number before either can reliably track which bytes have arrived. Two messages (SYN, SYN-ACK) would let the server confirm the client's ISN and propose its own, but the server would have no confirmation that the client actually received its ISN — a third message (ACK) closes that gap. This is why it's called a "three-way" handshake rather than two-way.

What is the congestion window and how is it different from the receive window?

The receive window is the receiver telling the sender how much buffer space it has — a flow-control limit. The congestion window (cwnd) is the sender's own estimate of how much data the network path can absorb without loss — a congestion-control limit. TCP always sends the minimum of the two, so a fast receiver with a huge buffer still won't be flooded onto a congested or high-latency path.

Why does slow start grow exponentially if it's supposed to be "slow"?

"Slow" refers to starting from one segment rather than blasting at full speed immediately — but the growth itself doubles every RTT (1, 2, 4, 8, 16…), which is exponential. It's slow only relative to sending an unlimited burst on the very first round trip; compared to the linear growth of congestion avoidance, slow start is actually the fast phase.

What triggers a "loss event" in real TCP — and why does this simulation model it as random?

Real TCP detects loss either via a retransmission timeout (no ACK arrives within the estimated RTT plus margin) or via three duplicate ACKs (the receiver keeps ACKing the last in-order byte because a later segment arrived out of order, implying the one before it was dropped). This simulation abstracts both into a single per-RTT loss probability so you can directly see how loss frequency shapes the sawtooth, without modelling every retransmission timer.

What is AIMD and why is it the "right" congestion-control shape?

Additive Increase, Multiplicative Decrease grows cwnd by a fixed amount each RTT when things are fine, and cuts it by a fixed fraction when loss is detected. Control theory shows AIMD converges toward fair bandwidth sharing among competing flows regardless of their starting point — an additive-increase/additive-decrease or multiplicative-increase/multiplicative-decrease scheme does not have this fairness guarantee, which is why Van Jacobson's original 1988 design chose AIMD and it has remained standard ever since.

Does modern TCP still use exactly this algorithm?

The core AIMD shape is still there, but production stacks layer refinements on top: TCP Reno adds fast recovery so the connection doesn't fall all the way back to slow start on an isolated loss, CUBIC (Linux's default) grows cwnd as a cubic function of time since the last loss to better use high-bandwidth links, and BBR estimates the path's bottleneck bandwidth and RTT directly instead of relying on loss as the primary signal. This simulation shows the classic Tahoe/Reno-style slow-start-then-AIMD behaviour those variants all build on.

Why does high RTT hurt throughput even without any packet loss?

Because cwnd grows once per round trip, a connection with a 300 ms RTT takes three times as long to reach the same window size as one with a 100 ms RTT — and every RTT also gates how often "permission" to send more data is renewed. Throughput is bounded by roughly cwnd·MSS / RTT, so doubling RTT halves throughput even if cwnd eventually reaches the same ceiling.

What is the Mathis formula and how accurate is it?

The Mathis formula approximates steady-state TCP throughput as MSS / (RTT · √(loss rate)), capturing the idea that throughput falls with higher RTT and rises only with the square root of a falling loss rate (i.e. loss hurts more than proportionally). It's a widely used back-of-envelope estimate, but it assumes steady-state congestion avoidance only — it ignores slow start, timeouts and receive-window limits, so real measured throughput can differ substantially, especially on short-lived or bursty connections.

Can a connection get "stuck" in slow start forever?

No — slow start always ends, either by reaching ssthresh and switching to congestion avoidance, or by hitting a loss event first, which sets a new (lower) ssthresh and restarts slow start from a small window. Because ssthresh is set adaptively from the cwnd at the moment of loss, it converges toward a value close to what the path can actually sustain.

⚙ Under the hood

Watch the real TCP three-way handshake (SYN → SYN-ACK → ACK), then a live AIMD congestion-window simulation showing slow start, congestion avoidance and multiplicative decrease on loss.

tcpnetworkingcongestion-controlaimdslow-starthandshake

2D · HTML5 Canvas 2D · 60 FPS target · runs fully client-side, no install

What did you find?

Add reproduction steps (optional)