Skip to content

Zoom-in: TCP

Karify98·
Cover Image for Zoom-in: TCP

Every HTTP request runs on TCP. But before the first byte of real data crosses the wire, three packets are exchanged carrying no payload at all. That's not wasted overhead — it's the price of a reliable channel on a network that guarantees nothing.

Zoom in.


Layer 1 — The problem: the Internet doesn't guarantee delivery

The Internet is a best-effort network. Each router tries to forward packets — no guarantees, no confirmations.

graph LR
    C["💻 Client"] -->|"packet 1"| I["🌐 Internet"]
    C -->|"packet 2"| I
    C -->|"packet 3"| I
    I -->|"packet 1 ✓"| S["🖥️ Server"]
    I -->|"packet 3 ✓"| S
    I -.->|"packet 2 ✗ dropped"| S
    style C fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
    style I fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
    style S fill:#1a3a2a,stroke:#22c55e,color:#86efac

Packets can be lost, arrive out of order, or be duplicated. IP has no mechanism to handle any of this. The layer above must.

Problem remaining: a mechanism above IP is needed to guarantee complete, ordered delivery. But before sending data, both sides need to confirm they're ready to communicate.

Layer 2 — 3-way handshake: opening a connection

Before any data is sent, TCP requires both sides to establish a connection in three steps.

sequenceDiagram
    participant C as Client
    participant S as Server

    C->>S: SYN (seq=100)
    Note right of C: "I want to connect.\nMy sequence number is 100."

    S-->>C: SYN-ACK (seq=300, ack=101)
    Note left of S: "OK. My sequence is 300.\nI received your seq 100."

    C->>S: ACK (ack=301)
    Note right of C: "I received your seq 300.\nConnection ready."

Each side picks a random sequence number at the start. That number is the foundation for reordering packets and detecting loss. Why 3 steps and not 2? Because both directions need to be confirmed — the client needs to know the server is listening, and the server needs to know the client received the response.

Problem remaining: connection is open. But during data transfer, how does either side know which packets were lost and need retransmission?

Layer 3 — Data transfer: sequence numbers and ACK

Every byte of data is numbered by sequence number. The receiver confirms receipt with ACK.

sequenceDiagram
    participant C as Client
    participant S as Server

    C->>S: DATA (seq=101, bytes 1-1000)
    C->>S: DATA (seq=1101, bytes 1001-2000)
    S-->>C: ACK (ack=2101) — "received up to byte 2100"

    C->>S: DATA (seq=2101, bytes 2001-3000)
    Note over C,S: ⚡ Packet dropped in transit

    Note over S: timeout — nothing received
    S-->>C: ACK (ack=2101) — "still waiting from byte 2101"
    C->>S: DATA (seq=2101) — retransmit

This is selective retransmission — only the lost packet is resent, not everything from the beginning. TCP also implements flow control (receiver window) and congestion control to avoid saturating the network.

Problem remaining: data transfer is done. The connection must be closed — but both sides need to agree that it's over.

Layer 4 — 4-way FIN: closing the connection

Closing requires 4 steps because the two directions are independent — each side declares it's done sending on its own.

sequenceDiagram
    participant C as Client
    participant S as Server

    C->>S: FIN
    Note right of C: "I'm done, won't send more"

    S-->>C: ACK
    Note left of S: "Got it"

    Note over S: Server may still be sending data

    S-->>C: FIN
    Note left of S: "I'm done too"

    C->>S: ACK
    Note right of C: "Got it. Wait 2×MSL then close."

    Note over C: TIME_WAIT (2 × MSL ≈ 60-120s)

TIME_WAIT is the state the client holds after sending the final ACK — to ensure that ACK actually reached the server. If the server didn't receive it, it will retransmit its FIN and the client needs to still be alive to respond. TIME_WAIT also prevents old sequence numbers from a previous connection from bleeding into a new one.


Full picture

sequenceDiagram
    participant C as Client
    participant S as Server

    Note over C,S: 🤝 Open connection
    C->>S: SYN (seq=100)
    S-->>C: SYN-ACK (seq=300, ack=101)
    C->>S: ACK (ack=301)

    Note over C,S: 📦 Transfer data
    C->>S: DATA (seq=101...)
    S-->>C: ACK
    S-->>C: DATA (seq=301...)
    C->>S: ACK

    Note over C,S: 👋 Close connection
    C->>S: FIN
    S-->>C: ACK
    S-->>C: FIN
    C->>S: ACK
    Note over C: TIME_WAIT (60-120s)

Takeaway

TCP exists because the Internet guarantees nothing. The handshake costs one round-trip before any data flows — that's the fixed price of every new TCP connection. HTTP/2 reduces this cost with multiplexing. HTTP/3 goes further by replacing TCP entirely with QUIC over UDP, handling reliability at the application layer.

When debugging unexpectedly high latency: check how many handshakes are happening. Keep-alive and connection pooling exist for exactly this reason — opening a new TCP connection is not free.


This post was assisted by Amy 🌸 - AI Assistant. Content has been reviewed by the author.

Related Posts