TCP Handshake
The TCP handshake is the three-message exchange — SYN, SYN-ACK, ACK — used for the normal active/passive TCP open. Each side picks an initial sequence number (ISN) and confirms it received the other side's, so both endpoints agree on where byte numbering starts before any application data flows. Three segments, two state machines, one shared agreement.
Think of it as two pen pals starting a numbered correspondence. You write "my letters will start at number 1000 — confirm you got this." Your pen pal writes back "received; I expect your letter 1001 next, and mine start at 5000." You confirm theirs. Only now does either of you trust that real mail will be counted correctly in both directions. That receipt-for-a-starting-number ritual is the entire handshake; everything below is just that letter exchange with TCP's vocabulary on it.
How the TCP Handshake Works
Before the first segment moves, the two endpoints are in asymmetric positions: the server sits in LISTEN, passively waiting, and the client is CLOSED — it has no connection state at all. The client makes the first move. It picks an ISN (here 1000), records that it is now mid-attempt by entering SYN-SENT, and sends a segment with the SYN flag and seq=1000. SYN literally means synchronize: "synchronize your numbering with mine, starting here."
The server, on reading that SYN, allocates connection state and enters SYN-RECEIVED — the half-open state, because only one direction has been agreed so far. Its reply does two jobs in one segment. It acknowledges the client's SYN with ack=1001, and it proposes its own numbering with its own SYN, seq=5000. Why 1001 and not 1000? Because a SYN consumes one sequence number even though it carries no data — the receipt always names the next letter expected, not the one just received.
The client reads that SYN-ACK, considers its side done, and enters ESTABLISHED. One debt remains: the server's SYN is still unacknowledged. So the client sends the final ACK with seq=1001, ack=5001, and when the server reads it, the server enters ESTABLISHED too. Both pen pals now hold the other's starting number plus a receipt for their own.
The client enters ESTABLISHED after receiving the SYN-ACK; the server enters it after receiving the final ACK. Between those events, the client considers the connection established while the server is still waiting for confirmation.
TCP Handshake Step by Step
The default scenario runs the normal handshake with clientIsn = 1000 and serverIsn = 5000. Here is the exact trace the animation plays:
- Init. Client is
CLOSED; server isLISTEN. No shared sequence context exists yet. - Client chooses its ISN: 1000. This number is fixed for the whole connection attempt.
- Client enters
SYN-SENT. State changes locally, before the server has seen anything. - Client sends
SYN seq=1000. The first letter crosses the wire. - Server reads the arriving SYN. Arrival first — the server has not changed state yet.
- Server enters
SYN-RECEIVED. The connection is now half-open, awaiting the final ACK. - Server computes
ack=1001. The SYN consumed sequence number 1000, so the next expected is 1001. - Server chooses its own ISN: 5000. Its SYN will consume one sequence number too.
- Server sends
SYN-ACK seq=5000, ack=1001. One segment, two jobs: propose and confirm. - Client reads the SYN-ACK. Its own SYN is now acknowledged.
- Client enters
ESTABLISHED. The client is ready; the server is still waiting. - Client computes
ack=5001to acknowledge the server's SYN — same receipt rule, applied in the other direction. - Client sends the final
ACK seq=1001, ack=5001. - Server reads the final ACK. It stays
SYN-RECEIVEDuntil it accepts it. - Server enters
ESTABLISHED. Both state machines now agree. - Done. The connection can carry application data.
On the canvas, each endpoint is a vertical lifeline with its state badges stacking downward as transitions happen — you can read CLOSED → SYN-SENT → ESTABLISHED straight down the client's line, LISTEN → SYN-RECEIVED → ESTABLISHED down the server's. Each segment glides between the lifelines as a card showing its flags, seq, and ack, and gets a longer beat than the bookkeeping steps; segments already delivered fade into a dim trace so the one currently in flight is always the brightest thing on screen.
Why Three Messages?
Two would not be enough, and four would waste a trip. The handshake must establish reliable numbering in both directions, and "reliable" here means acknowledged. The client's SYN proposes numbering for the client→server direction; the server's ACK confirms it. The server's SYN proposes numbering for the server→client direction; the client's ACK confirms it. That is four logical jobs — but the server's ACK and the server's SYN ride in the same segment, the SYN-ACK, collapsing four messages into three. The pen-pal version: your friend's reply both confirms your starting number and announces theirs, so one envelope does double duty.
Drop the third message and the server's SYN goes unconfirmed — the server can never distinguish "client agreed" from "client vanished," and it would sit in SYN-RECEIVED holding state for a peer that may not exist. The third ACK is what lets the server commit.
Edge Cases
The Lost SYN-ACK preset replays the same connection with one failure injected: the server's SYN-ACK is lost in transit. The trace diverges after step 9 above.
The client, stuck in SYN-SENT, hears nothing. Its retransmission timer fires, and it resends the same SYN with the same seq=1000 — the same letter, not a new correspondence. This matters: changing the ISN would no longer retransmit the same SYN. Connection identification also uses endpoint addresses and ports; a changed ISN does not imply the server simply allocates a second connection on the same tuple. Instead the server, already in SYN-RECEIVED, recognizes the duplicate SYN and allocates nothing new; it simply resends its SYN-ACK with the same seq=5000, ack=1001. This time it arrives, and the handshake completes exactly as in steps 10–16. Loss costs time, never correctness — the sequence numbers make retransmission idempotent.
Common Mistakes
- Reading the ACK number as "the byte I just received." It is "the next sequence number I expect from you." Acknowledging
seq=1000producesack=1001, and acknowledgingseq=5000producesack=5001— the receipt names the next letter, always. - Forgetting that a SYN consumes a sequence number. It carries no data, yet
ack = seq + 1. Miss this and every number in the trace looks off by one. - Assuming both sides become
ESTABLISHEDat the same moment. The client commits on receiving the SYN-ACK; the server commits a full segment later, on receiving the final ACK. The half-openSYN-RECEIVEDwindow in between is real, and it is exactly what SYN-flood attacks exploit at scale. - Treating a retransmitted SYN as a new connection. The same connection attempt retransmits the same SYN and ISN; the connection is also identified by its endpoint tuple. The retry repeats the unacknowledged segment; it never restarts the negotiation.
- Thinking the ISNs are always small round numbers. The
1000and5000here are chosen for readability; real stacks randomize ISNs precisely so old or forged segments cannot masquerade as part of a live connection.
A Note on Simplification
This visualization deliberately stays small: it omits TCP options, receive windows, SYN cookies, simultaneous open, and real RTT-based timeout estimation so the three-segment state machine stays readable. The numbers and state transitions are the real protocol; the timer is simplified.
Keep exploring
A few lessons that share the same technique or lead into the next idea.
Found a mistake or something unclear? Leave feedback.