How Unicast Works — Part 4: TCP, The Handshake, Sequence Numbers & Reliable Delivery
Deep Dive Series · Networks
How Unicast Works — Part 4: TCP, The Handshake, Sequence Numbers & Reliable Delivery
Deep Dive Series · Networks
IP is a gambler. It flings packets into the network and hopes for the best — no guarantees of delivery, no guarantee of order, no guarantee anything arrives at all. That’s fine for speed, but most applications need something sturdier. They need to know their data arrived complete, intact, and in the right sequence. That contract is fulfilled by TCP — Transmission Control Protocol. And it’s been doing so, quietly and reliably, since 1981.
The TCP Header: 20 Bytes of Promise
Like IP, TCP leads with a header. Here’s every field and what it does:

Field Size Purpose Source / Dest Port 16 bits each Identifies the application on each end Sequence Number 32 bits Byte position of the first byte in this segment Acknowledgement Number 32 bits Next byte the receiver expects to receive Flags 9 bits SYN, ACK, FIN, RST, PSH, URG — control connection state Window Size 16 bits Receiver’s available buffer space — flow control Checksum 16 bits Error detection Options variable MSS, Window Scaling, SACK, Timestamps
TCP Flags: The Control Signals
Nine bits that change the meaning of every segment:

The Three-Way Handshake
Before any application data flows, TCP requires both sides to establish synchronized state via the three-way handshake. Its purpose: exchange Initial Sequence Numbers (ISNs) and verify bidirectional communication.

Why random ISNs (Initial Sequence Numbers)? Security. Predictable sequence numbers allow TCP sequence number prediction attacks — an attacker could forge segments by guessing the number. Randomization prevents this.
The ACK Rule: The Acknowledgement Number always means “I’ve received everything up to byte N-1 — send me byte N next.” When the server sends ACK=1001, it means: “I received your SYN (counts as 1 byte). Send byte 1001 next.”
Sequence Numbers: The Byte Counter
Sequence numbers don’t number segments — they number bytes. Every byte in the stream has a unique position.
Handshake complete. Client ISN=1000, Server ISN=5000.
Client sends HTTP request (40 bytes): "GET /index.html HTTP/1.1..."
CLIENT → SERVER
Flags: PSH, ACK
Seq: 1001 ← "this segment starts at byte 1001"
Ack: 5001 ← "I've received server bytes up to 5000"
Data: [40 bytes of HTTP request]
SERVER → CLIENT (acknowledges)
Flags: ACK
Seq: 5001
Ack: 1041 ← "received bytes 1001–1040, send 1041 next"
(1001 + 40 = 1041)
SERVER → CLIENT (HTTP response, 1400 bytes)
Flags: PSH, ACK
Seq: 5001
Ack: 1041
Data: "HTTP/1.1 200 OK..." [1400 bytes]
CLIENT → SERVER (acknowledges response)
Flags: ACK
Seq: 1041
Ack: 6401 ← "received bytes 5001–6400, send 6401 next"
This byte-level accounting lets TCP detect exactly which bytes are lost, retransmit only those, and reassemble them in order — even if segments arrive out of order.
Reliability: What Happens When a Packet Is Lost
IP packets get dropped — by overflowing router buffers, link glitches, anything. TCP’s recovery is methodical:
Retransmission Timer: When a segment is sent, a timer starts. No ACK before expiry → retransmit.
Fast Retransmit: Three duplicate ACKs (the receiver keeps asking for the same byte) signal a specific segment is missing. The sender retransmits immediately — no waiting for the timer.
Selective Acknowledgement (SACK): Without SACK, the sender might retransmit already-received segments. SACK lets the receiver say: “I’m missing bytes X–Y, but I have Z–W.” The sender retransmits only the gap.
Sender transmits segments 1–5. Segment 3 is lost.
SENDER → [1] [2] [3 ✗LOST] [4] [5]
← RECEIVER ACK=2 ACK=3 ACK=3(dup) ACK=3(dup) ACK=3(dup)
SACK: {4–5 received}
3× duplicate ACKs → Fast Retransmit fires immediately
SENDER → [3 retransmit] ← only segment 3
← RECEIVER ACK=6 ← "got everything, send 6 next"
Stream complete. Cost: 1 retransmit.
Flow Control: The Sliding Window
TCP must avoid overwhelming the receiver. The receive window (16-bit field in every segment) tells the sender: “I have this much buffer space — don’t exceed it.”
Sender's view — window size = 4 segments:
┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│ 1 ✓ │ 2 ✓ │ 3 ✓ │ 4 → │ 5 → │ 6 │ 7 │ 8 │ 9 │
│ acked│ acked│ acked│inflt │inflt │ can │ can │block │block │
└──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
│←────── window ──────→│
- 1–3 (acked): Sent and confirmed. Sender can forget them.
- 4–5 (in-flight): Sent, awaiting ACK.
- 6–7 (can send): Within window, can be sent immediately.
- 8–9 (blocked): Beyond current window. Must wait for ACKs.
As ACKs arrive, the window slides right. This is the sliding window.
Window Scaling (a TCP option negotiated at handshake) extends the window to effectively 30 bits — essential for high-bandwidth, high-latency links where you need megabytes in-flight simultaneously to fully utilize the connection.
Congestion Control: Saving the Internet from Itself
Flow control protects the receiver. Congestion control protects the network. In 1986, the internet suffered its first congestion collapse — senders blasting at full speed, router buffers overflowing, throughput dropping to nearly zero. Van Jacobson’s algorithms saved it.
TCP maintains a congestion window (cwnd). Actual sending rate = min(receive window, cwnd). Here’s how cwnd evolves:
cwnd
│
16│ ╭───╮
│ ╭───╯ │
8│ ╭────╯ │ ssthresh
│ ╭───╯ │ ╭──────────────────
4│ ╭───╯ │ │ Congestion Avoidance
│╭╯ Slow Start ↓ │ (+1 per RTT)
1│─╯ (doubles/RTT) LOSS │
└──────────────────────────────────────────────→ time (RTTs)
│← Slow Start →│ │← Slow Start →│← CA
The four phases:
- Slow Start — begin with cwnd=1. Double every RTT. Exponential — ends fast.
- Congestion Avoidance — when cwnd reaches
ssthresh, grow +1 per RTT. Linear probing. - Loss Detection — 3 dup ACKs → halve cwnd (TCP Reno). Timeout → cwnd=1. Update ssthresh.
- Recovery — grow again from new cwnd toward new threshold.
Modern variants: TCP Cubic (Linux default since 2006) uses a cubic growth curve for faster recovery on high-bandwidth links. Google’s QUIC (HTTP/3) replaces TCP with a UDP-based protocol that implements its own congestion control and eliminates TCP’s head-of-line blocking — where one lost packet stalls all subsequent data even if it has already arrived.
TCP Connection Teardown
Both sides must independently signal they’re done sending — a four-way FIN sequence:
CLIENT SERVER
FIN, Seq=X ────────→ "I'm done sending"
←──────── ACK, Ack=X+1
(server can still send data — half-close)
←──────── FIN, Seq=Y "I'm done too"
ACK, Ack=Y+1 ────────→
→ Client enters TIME_WAIT (2 × MSL ≈ 4 minutes)
→ Absorbs any delayed packets before port reuse
→ Both sides: CLOSED
The TIME_WAIT state is why restarting a server quickly sometimes fails to rebind the same port — the OS holds it open briefly. SO_REUSEADDR bypasses this for development.
TCP States: The Full Lifecycle
State Meaning CLOSED No connection. Initial and final state. LISTEN Server waiting for incoming SYN. SYN_SENT Client sent SYN, awaiting SYN-ACK. SYN_RCVD Server received SYN, sent SYN-ACK, awaiting ACK. ESTABLISHED Handshake done. Data is flowing. Steady state. FIN_WAIT_1/2 This side sent FIN, waiting for the other's response. CLOSE_WAIT Received remote FIN. May still send data before our FIN. TIME_WAIT All FINs exchanged. Waiting ~4min before port reuse.
The Full Stack: Everything Together
One HTTPS request. All four layers.
YOU press Enter on "https://example.com"
── LAYER 7 (Application) ──────────────────────────────────────
Browser: "GET /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n"
TLS encrypts it → opaque ciphertext
── LAYER 4 (Transport — TCP) ──────────────────────────────────
3-way handshake on port 443 (SYN / SYN-ACK / ACK)
Segment: Src=54321 Dst=443 Seq=1001 Ack=5001 PSH,ACK
[encrypted HTTP payload inside]
── LAYER 3 (Network — IP) ─────────────────────────────────────
Packet: Src=192.168.1.5 Dst=93.184.216.34 TTL=64 Proto=6(TCP)
[TCP segment inside]
── LAYER 2 (Data Link — Ethernet) ─────────────────────────────
Frame: Dst=f4:6d:04:a1:b2:c3 Src=ac:de:48:00:11:22 Type=0x0800
[IP packet inside] + FCS checksum
── LAYER 1 (Physical) ──────────────────────────────────────────
Electrical pulses / Wi-Fi radio / fiber light
10101100110101001010110010110011...
──────────────────── ACROSS THE NETWORK ────────────────────────
L2 frame stripped at router (ARP resolved MAC)
IP packet hops 9 routers (TTL: 64 → 55)
NAT rewrites source IP at ISP boundary
BGP routes guide through backbone
────────────────────────────────────────────────────────────────
AT EXAMPLE.COM — reverse decapsulation:
L1: bits → bytes
L2: FCS verified → IP packet extracted
L3: IP header → TCP segment extracted
L4: TCP reassembles segments in order → TLS record handed up
L5/6: TLS decrypts → plaintext HTTP
L7: "GET /index.html" → web server → sends response
→ Response travels back. Same journey, reversed.
→ TCP reassembles all segments. TLS decrypts. Browser renders.
→ Page loads. ~50–100ms. Every single time you press Enter.
What You’ve Learned in Part 4
- The TCP header carries ports, sequence/acknowledgement numbers, flags, and window size
- Flags (SYN, ACK, FIN, RST, PSH) control connection state with surgical precision
- The 3-way handshake synchronizes both sides before any data flows
- Sequence numbers track individual bytes — TCP can detect gaps and retransmit only what’s lost
- Fast Retransmit and SACK recover from loss faster than waiting for timeouts
- The sliding window lets the receiver throttle the sender to match its buffer capacity
- Congestion control (Slow Start + Congestion Avoidance) keeps the internet from collapsing
- The 4-way FIN + TIME_WAIT tears down connections without losing in-flight data
메타데이터
- post_id
- 8ac70fc563c8
- slug
- how-unicast-works-part-4-tcp-the-handshake-sequence-numbers-reliable-delivery-8ac70fc563c8
- url
- https://medium.com/@lambdafunc/how-unicast-works-part-4-tcp-the-handshake-sequence-numbers-reliable-delivery-8ac70fc563c8
- canonical_url
- https://medium.com/@lambdafunc/how-unicast-works-part-4-tcp-the-handshake-sequence-numbers-reliable-delivery-8ac70fc563c8
- author_url
- https://medium.com/@lambdafunc
- status
- ok
- fetched_at
- 2026-06-09 15:37:30