Network: TCP/IP và HTTP nền tảng
1. OSI Model & TCP/IP Stack
OSI 7 tầng vs TCP/IP 4 tầng
OSI Model TCP/IP Model Giao thức thực tế
─────────────────────────────────────────────────────────────
7. Application ┐
6. Presentation ├──► Application HTTP, HTTPS, DNS, gRPC
5. Session ┘
4. Transport ────► Transport TCP, UDP, QUIC
3. Network ────► Internet IP, ICMP, BGP
2. Data Link ┐
1. Physical ┴──► Network Access Ethernet, WiFi, Fiber
OSI là mô hình lý thuyết được dạy trong sách giáo khoa — hữu ích để phân tích, nhưng không ai "implement" đúng 7 tầng đó. TCP/IP là thứ thực sự chạy trên internet, với 4 tầng gọn hơn.
Dữ liệu đi qua các tầng như thế nào?
Mỗi tầng chỉ nói chuyện với tầng ngay cạnh nó. Khi gửi, mỗi tầng bọc thêm một lớp header vào ngoài dữ liệu — gọi là encapsulation. Khi nhận, quá trình ngược lại: mỗi tầng bóc lớp header của mình ra rồi chuyển lên trên.
Gửi: Data → [App header | Data] (Application)
→ [TCP header | App header | Data] (Transport)
→ [IP header | TCP header | Data] (Network)
→ [ETH header | IP header | TCP | Data] (Data Link)
→ bits trên dây (Physical)
Nhận: Quá trình ngược lại — mỗi tầng bóc header của mình ra
2. TCP — Chi tiết kỹ thuật
TCP Header format
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
┌─────────────────────────┬─────────────────────────────────────┐
│ Source Port │ Destination Port │
├─────────────────────────┴─────────────────────────────────────┤
│ Sequence Number │
├───────────────────────────────────────────────────────────────┤
│ Acknowledgment Number │
├────────┬───────┬─┬─┬─┬─┬─┬─┬──────────────────────────────────┤
│Data Off│Reserv │U│A│P│R│S│F│ Window Size │
│ (4) │ (6) │R│C│S│S│Y│I│ (16 bits) │
│ │ │G│K│H│T│N│N│ │
├────────────────┴─┴─┴─┴─┴─┴─┴──────────────────────────────────┤
│ Checksum │ Urgent Pointer │
└───────────────────────────────────────────────────────────────┘
3-Way Handshake
Trước khi trao đổi dữ liệu, TCP phải thiết lập kết nối qua 3 bước. Cả hai bên thỏa thuận sequence number ban đầu để theo dõi thứ tự packet.
Client Server
│ │
│──── SYN (seq=x) ─────────────►│ "Tôi muốn kết nối, seq của tôi là x"
│ │
│◄─── SYN-ACK (seq=y, ack=x+1) ─│ "OK, seq của tôi là y, tôi nhận được x"
│ │
│──── ACK (ack=y+1) ───────────►│ "Tôi nhận được y, kết nối thành công"
│ │
│ [Data Transfer] │
Chi phí của handshake là 1 RTT (Round Trip Time) — client phải chờ trọn một vòng đi-về trước khi gửi được byte data đầu tiên.
4-Way Termination
Đóng kết nối cần 4 bước vì mỗi chiều phải đóng độc lập. Sau khi gửi ACK cuối, client chờ thêm 2×MSL (~60 giây) ở trạng thái TIME_WAIT để đảm bảo ACK đã tới nơi.
Client Server
│──── FIN ────────────────────►│
│◄─── ACK ─────────────────────│
│◄─── FIN ─────────────────────│
│──── ACK ────────────────────►│
│ (wait 2×MSL = ~60s) │ TIME_WAIT state
Flow Control & Congestion Control
TCP có hai cơ chế kiểm soát lưu lượng với mục tiêu khác nhau:
Flow Control — tránh làm quá tải receiver. Receiver thông báo còn bao nhiêu buffer trống qua trường Window Size trong header. Sender không được gửi vượt quá giá trị đó.
Congestion Control — tránh làm quá tải network. Đây là bài toán khó hơn vì không có ai thông báo — TCP phải tự suy luận từ việc mất gói.
Slow Start:
cwnd = 1 MSS
Mỗi ACK → cwnd × 2 (tăng theo hàm mũ)
Đến ssthresh → chuyển sang Congestion Avoidance
Congestion Avoidance:
cwnd tăng +1 MSS mỗi RTT (tăng tuyến tính)
Khi mất gói → cwnd giảm mạnh → slow start lại
Linux dùng TCP Cubic mặc định; Google dùng BBR — cả hai đều implement ý tưởng trên nhưng với thuật toán tinh vi hơn.
Tại sao TCP tốn kém?
Mỗi kết nối mới:
1. 3-way handshake → 1 RTT
2. TLS handshake → 1-2 RTT thêm
3. Slow start → vài RTT để đạt throughput tối đa
Tổng: 3-4 RTT trước khi nhận được byte data đầu tiên!
→ Đây là lý do HTTP/2 và HTTP/3 ra đời
3. HTTP/1.1 — Nền tảng
Request/Response format (plaintext)
HTTP/1.1 là text thuần. Request và response đều có format cố định: dòng đầu (method/status), headers, rồi blank line, rồi body.
Request:
GET /api/users HTTP/1.1\r\n
Host: example.com\r\n
Accept: application/json\r\n
Authorization: Bearer token123\r\n
\r\n ← blank line kết thúc headers
[body nếu có]
Response:
HTTP/1.1 200 OK\r\n
Content-Type: application/json\r\n
Content-Length: 42\r\n
\r\n
{"id": 1, "name": "Alice"}
Vấn đề của HTTP/1.1
Head-of-Line (HOL) Blocking
HTTP/1.1 xử lý request theo thứ tự nghiêm ngặt: phải nhận xong response của request trước mới gửi được request tiếp. Nếu request đầu chậm, tất cả phải xếp hàng chờ.
Connection: [REQ 1]──►[RES 1]──►[REQ 2]──►[RES 2]──►[REQ 3]──►[RES 3]
↑
Phải chờ RES 1 xong mới gửi REQ 2 được
HTTP pipelining cho phép gửi nhiều request liên tiếp mà không chờ response, nhưng response vẫn phải về đúng thứ tự — nên HOL blocking vẫn còn. Hầu hết browser đã vô hiệu hóa tính năng này.
Header dư thừa
Mỗi request gửi lại toàn bộ headers, kể cả những thứ không bao giờ đổi như User-Agent, Cookie, Authorization. Với application nhiều request, đây là lãng phí bandwidth đáng kể.
Workarounds thời HTTP/1.1
Vì browser chỉ mở tối đa 6 connection/domain, dev time đó phải dùng các trick:
- Domain Sharding: tách static files sang
cdn1.example.com,cdn2.example.com... để có thêm kết nối. Cồng kềnh và tốn thêm DNS lookup + TCP handshake overhead. - Bundling/Spriting: gộp nhiều JS/CSS thành một file to, gộp nhiều icon thành một sprite image. Không thanh lịch và gây khó khăn cho cache invalidation.
4. HTTP/2 — Binary, Multiplexing, HPACK
RFC 7540 (2015). HTTP/2 giữ nguyên semantics của HTTP/1.1 (cùng methods, status codes, headers) nhưng thay đổi hoàn toàn cách truyền dữ liệu trên wire.
4.1 Binary Framing Layer
HTTP/1.1 là plaintext — con người đọc được, nhưng máy phải parse chậm. HTTP/2 chuyển sang binary frames: cấu trúc cố định, parse nhanh hơn, ít lỗi hơn.
HTTP/2 binary frame:
┌──────────────────────────────────────────┐
│ Frame Format │
├────────────┬──────────┬──────────────────┤
│ Length (24)│Type (8) │ Flags (8) │
├────────────┴──────────┴──────────────────┤
│ R │ Stream Identifier (31 bits) │
├───┴──────────────────────────────────────┤
│ Frame Payload │
└──────────────────────────────────────────┘
Frame Types
| Type | ID | Mô tả |
|---|---|---|
| DATA | 0x0 | Chứa body của request/response |
| HEADERS | 0x1 | Chứa headers (compressed bằng HPACK) |
| PRIORITY | 0x2 | Đặt độ ưu tiên stream |
| RST_STREAM | 0x3 | Hủy một stream |
| SETTINGS | 0x4 | Thay đổi cấu hình connection |
| PUSH_PROMISE | 0x5 | Server push |
| PING | 0x6 | Kiểm tra connection còn sống |
| GOAWAY | 0x7 | Đóng connection |
| WINDOW_UPDATE | 0x8 | Flow control |
| CONTINUATION | 0x9 | Tiếp tục HEADERS frame |
4.2 Streams & Multiplexing
Đây là cải tiến lớn nhất của HTTP/2. Thay vì mỗi request cần một TCP connection riêng, HTTP/2 dùng khái niệm stream — một cặp request/response logic chạy trên cùng một TCP connection.
HTTP/2 connection = 1 TCP connection chứa nhiều STREAMS song song
Stream IDs:
Client-initiated: 1, 3, 5, 7... (số lẻ)
Server-initiated: 2, 4, 6, 8... (số chẵn, dùng cho server push)
Ví dụ trên cùng 1 TCP connection:
Stream 1: GET /index.html ─────────────────►
Stream 3: GET /style.css ───────────────────►
Stream 5: GET /app.js ────────────────►
Stream 7: POST /api/data ─────►
(tất cả đồng thời!)
Frame interleaving trên wire:
[S1:HEADERS][S3:HEADERS][S5:HEADERS][S1:DATA][S3:DATA][S7:HEADERS][S5:DATA]...
Kết quả là không còn cần domain sharding hay bundling — browser có thể gửi hàng chục requests song song trên 1 connection.
HTTP/1.1: [====REQ1====][====REQ2====][====REQ3====] (tuần tự)
HTTP/2: [=REQ1=]
[===REQ2===] (song song)
[==REQ3==]
⚠️ HTTP/2 giải quyết HOL blocking ở application layer nhưng vẫn còn HOL blocking ở TCP layer. Nếu 1 TCP packet bị mất → tất cả streams phải chờ retransmission. HTTP/3 mới giải quyết triệt để vấn đề này.
4.3 HPACK — Header Compression
RFC 7541. HPACK giải quyết vấn đề header dư thừa bằng 3 kỹ thuật kết hợp.
1. Static Table — 61 entries được định nghĩa sẵn trong spec. Các header phổ biến như :method: GET hay :path: / chỉ cần gửi một index 1-2 byte thay vì full string.
Index │ Header Name │ Header Value
──────┼──────────────────────┼──────────────
2 │ :method │ GET
4 │ :path │ /
8 │ :status │ 200
...
→ "GET /" chỉ cần gửi index 2 và index 4 — 2 bytes thay vì 14 bytes!
2. Dynamic Table — được xây dựng trong quá trình kết nối. Lần đầu gửi header mới thì gửi full string và thêm vào bảng. Lần sau chỉ cần gửi index.
Lần đầu: Authorization: Bearer abc123 → gửi full + thêm vào index 62
Lần sau: → chỉ gửi index 62 — 1-3 bytes thay vì 28+ bytes!
3. Huffman Encoding — thay ASCII 8-bit bằng Huffman code: ký tự hay gặp dùng ít bit hơn, ký tự hiếm dùng nhiều bit hơn. Trung bình giảm ~30% kích thước headers.
4.4 Stream Priority
Stream có thể được tổ chức thành dependency tree với weight. Bandwidth được phân bổ theo tỉ lệ weight — ví dụ: HTML trước, CSS/JS sau, images cuối.
Stream 1 (weight=16)
/ \
Stream 3 Stream 5 (weight=32)
(weight=16) / \
Stream 7 Stream 9
(weight=8) (weight=16)
4.5 Server Push
Server có thể chủ động gửi resources mà không cần client request. Ví dụ khi client hỏi index.html, server biết client sẽ cần style.css và app.js nên push luôn.
Trong thực tế tính năng này ít hiệu quả vì server không biết client đã cache gì. Chrome đã deprecated HTTP/2 push năm 2022.
4.6 Connection Preface
Client bắt đầu HTTP/2 connection bằng:
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n ← "magic bytes"
+ SETTINGS frame
Server response:
+ SETTINGS frame
+ SETTINGS ACK