Skip to content

Zoom-in: TCP

Karify98·
Cover Image for Zoom-in: TCP

Mọi HTTP request đều đi trên TCP. Nhưng trước khi byte đầu tiên của dữ liệu đi qua, đã có 3 gói tin trao đổi mà không mang dữ liệu nào. Đó không phải overhead thừa — đó là giá phải trả để có một kênh truyền đáng tin cậy trên một mạng vốn không đảm bảo điều gì.

Phóng to dần vào đó.


Layer 1 — Vấn đề: Internet không đảm bảo giao hàng

Internet là một mạng best-effort. Mỗi router chỉ cố gắng chuyển tiếp gói tin — không có cam kết, không có xác nhận.

graph LR
    C["💻 Client"] -->|"gói 1"| I["🌐 Internet"]
    C -->|"gói 2"| I
    C -->|"gói 3"| I
    I -->|"gói 1 ✓"| S["🖥️ Server"]
    I -->|"gói 3 ✓"| S
    I -.->|"gói 2 ✗ mất"| 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

Gói tin có thể mất, đến không đúng thứ tự, hoặc trùng lặp. IP không có cơ chế xử lý những trường hợp này. Tầng trên phải tự lo.

Vấn đề còn lại: cần một cơ chế ở trên IP để đảm bảo dữ liệu đến đủ, đúng thứ tự. Nhưng trước khi truyền dữ liệu, hai bên phải biết nhau có sẵn sàng không.

Layer 2 — 3-way handshake: mở kết nối

Trước khi gửi dữ liệu, TCP yêu cầu hai bên thiết lập kết nối qua 3 bước.

sequenceDiagram
    participant C as Client
    participant S as Server

    C->>S: SYN (seq=100)
    Note right of C: "Tôi muốn kết nối,\nsequence number của tôi là 100"

    S-->>C: SYN-ACK (seq=300, ack=101)
    Note left of S: "OK. Sequence của tôi là 300.\nTôi nhận được seq 100 của bạn."

    C->>S: ACK (ack=301)
    Note right of C: "Tôi nhận được seq 300 của bạn.\nKết nối sẵn sàng."

Mỗi bên chọn một sequence number ngẫu nhiên khi bắt đầu. Sequence number này là nền tảng để sắp xếp lại gói tin và phát hiện gói bị mất. Tại sao 3 bước chứ không phải 2? Vì cần xác nhận theo cả hai chiều — client cần biết server nghe được, và server cần biết client nhận được phản hồi.

Vấn đề còn lại: kết nối mở rồi. Nhưng khi truyền dữ liệu, làm sao biết gói nào bị mất để gửi lại?

Layer 3 — Truyền dữ liệu: sequence number và ACK

Mỗi byte dữ liệu được đánh số theo sequence number. Bên nhận xác nhận bằng 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) — "nhận đủ đến byte 2100"

    C->>S: DATA (seq=2101, bytes 2001-3000)
    Note over C,S: ⚡ Gói tin bị mất trên đường

    Note over S: timeout — không nhận được
    S-->>C: ACK (ack=2101) — "vẫn đợi từ byte 2101"
    C->>S: DATA (seq=2101) — gửi lại

Cơ chế này gọi là selective retransmission — chỉ gửi lại gói bị mất, không gửi lại từ đầu. TCP cũng có flow control (receiver window) và congestion control để không làm nghẽn mạng.

Vấn đề còn lại: dữ liệu đã truyền xong. Phải đóng kết nối — nhưng đảm bảo cả hai bên đều biết kết nối đã kết thúc.

Layer 4 — 4-way FIN: đóng kết nối

Đóng kết nối cần 4 bước vì hai chiều độc lập — mỗi bên tự tuyên bố xong việc.

sequenceDiagram
    participant C as Client
    participant S as Server

    C->>S: FIN
    Note right of C: "Tôi xong rồi, không gửi nữa"

    S-->>C: ACK
    Note left of S: "OK, nhận được"

    Note over S: Server có thể vẫn đang gửi dữ liệu

    S-->>C: FIN
    Note left of S: "Tôi cũng xong rồi"

    C->>S: ACK
    Note right of C: "OK. Chờ 2×MSL rồi đóng hẳn."

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

TIME_WAIT là trạng thái client giữ sau khi gửi ACK cuối — để đảm bảo ACK đó đến được server. Nếu server không nhận ACK, nó sẽ gửi lại FIN và client cần còn "sống" để phản hồi. TIME_WAIT cũng ngăn sequence number cũ từ connection trước gây nhầm lẫn trong connection mới.


Full picture

sequenceDiagram
    participant C as Client
    participant S as Server

    Note over C,S: 🤝 Mở kết nối
    C->>S: SYN (seq=100)
    S-->>C: SYN-ACK (seq=300, ack=101)
    C->>S: ACK (ack=301)

    Note over C,S: 📦 Truyền dữ liệu
    C->>S: DATA (seq=101...)
    S-->>C: ACK
    S-->>C: DATA (seq=301...)
    C->>S: ACK

    Note over C,S: 👋 Đóng kết nối
    C->>S: FIN
    S-->>C: ACK
    S-->>C: FIN
    C->>S: ACK
    Note over C: TIME_WAIT (60-120s)

Takeaway

TCP tồn tại vì Internet không đảm bảo điều gì. Handshake tốn 1 RTT trước khi dữ liệu đi — đó là chi phí cố định của mọi kết nối TCP mới. HTTP/2 giảm thiểu chi phí này bằng multiplexing. HTTP/3 đi xa hơn bằng cách bỏ hẳn TCP, dùng QUIC trên UDP để tự xử lý reliability.

Khi debug latency cao bất thường: kiểm tra số lần handshake. Keep-alive và connection pooling tồn tại đúng vì lý do này — mở kết nối mới không miễn phí.


Bài viết được hỗ trợ bởi Amy 🌸 - AI Assistant. Nội dung đã được kiểm duyệt bởi tác giả.

Related Posts