3.1 TCP là gì?
TCP (Transmission Control Protocol) cung cấp 4 đảm bảo:
- Connection-oriented: 2 bên thiết lập connection trước, terminate sau
- Reliable: data đến đầy đủ — packet mất → retransmit
- In-order: data đến đúng thứ tự — packet đến lộn xộn → reorder
- Flow + Congestion control: không "ngập" receiver hay network
Đánh đổi: TCP chậm hơn UDP do overhead. Nhưng đáng tin → dùng cho hầu hết app cần data toàn vẹn (HTTP, SSH, file transfer).
Dùng port để phân biệt app
TCP segment có source port và destination port. OS dùng port để biết giao data cho app nào.
- Port 0-1023: well-known (cần root): 80=HTTP, 443=HTTPS, 22=SSH, 25=SMTP
- Port 1024-49151: registered
- Port 49152-65535: ephemeral — client tự cấp khi connect ra
3.2 TCP Header — 20 byte
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 | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options (variable) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Các trường quan trọng
- Source/Dest Port (16-bit): xác định app
- Sequence Number (32-bit): số thứ tự byte đầu tiên trong segment
- ACK Number (32-bit): số byte tiếp theo mong nhận từ peer
- Flags (6 bit): SYN, ACK, FIN, RST, PSH, URG
- Window (16-bit): còn dung lượng nhận thêm bao nhiêu byte
- Checksum: phát hiện lỗi bit
3.3 3-way Handshake — mở connection
Trước khi gửi data, 2 bên phải "bắt tay". Dùng 3 packet:
Tại sao phải 3 packet?
- Packet 1 (Client→Server): "Tôi muốn connect, đây là seq tôi" — server biết client tồn tại + seq client
- Packet 2 (Server→Client): ACK packet 1 + gửi seq của server — client biết server alive + seq server
- Packet 3 (Client→Server): ACK packet 2 — server biết client nhận được seq server
Sau 3 packet: cả 2 bên đều biết seq của nhau và confirm 2 chiều. Đây là yêu cầu tối thiểu cho reliable connection.
Hệ quả của 3-way handshake
- 1.5 RTT để bắt tay xong → trước HTTP/3, mỗi HTTPS connection mất ~3 RTT (TCP + TLS)
- SYN flood attack: gửi nhiều SYN, server giữ half-open, hết resource
- Defense: SYN cookies, rate limit
3.4 4-way Termination — đóng connection
Đóng connection cần 4 packet (vì TCP full-duplex, mỗi chiều đóng riêng):
4 packet vì: mỗi bên có FIN + ACK. Nếu đồng thời cả 2 bên FIN, có thể "merge" → 3 packet (rare).
3.5 Sequence Number & ACK
TCP đánh số từng byte trong stream. Seq # của segment = số byte đầu trong segment.
Client gửi 100 byte với seq=1000:
Segment: seq=1000, data="A...Z..." (100 bytes)
Server nhận, ACK = 1100 ("byte tiếp theo tôi muốn = 1100"):
Segment: ack=1100
Client gửi tiếp 50 byte:
Segment: seq=1100, data="X...Y..." (50 bytes)
Server: ack=1150
...
Cumulative ACK
ACK=N nghĩa là "đã nhận đủ đến byte N-1, mong byte N tiếp theo". 1 ACK xác nhận tất cả byte trước.
Retransmission
Nếu sender không nhận ACK trong RTO (Retransmission Timeout), gửi lại segment. RTO được tính dựa trên RTT measure.
SACK (Selective Acknowledgment)
Cumulative ACK có vấn đề: nếu mất segment 5 trong 10 segment, sender phải gửi lại 5-10. SACK cho phép báo "tôi nhận 1-4, 6-10, thiếu 5" → chỉ retransmit segment 5.
3.6 Sliding Window — Flow Control
Vấn đề: sender gửi quá nhanh, receiver không kịp xử lý → buffer đầy → drop. Phải có cơ chế "phanh".
Giải pháp: mỗi ACK packet, receiver thông báo Window = "tôi còn chỗ chứa thêm X byte". Sender không gửi quá X byte chưa được ACK.
Window scaling
Field Window 16-bit → max 64KB. Trên link tốc độ cao (10 Gbps), 64KB là quá nhỏ. Option Window Scale (TCP option) nhân thêm 2^N → max ~1GB.
Zero window probe
Nếu receiver advertise window=0 (đầy), sender vẫn định kỳ gửi packet 1-byte để "thăm dò" — nếu không, có thể stuck mãi.
3.7 Congestion Control — đừng làm sập network
Flow control = không "ngập" receiver. Congestion control = không "ngập" network (router buffer, link capacity).
TCP giả định: packet loss = network nghẽn. Khi loss, giảm tốc độ.
Congestion Window (cwnd)
Sender có 2 limit: rwnd (receive window từ receiver) và cwnd (do sender tự điều chỉnh). Số byte in-flight tối đa = min(rwnd, cwnd).
Slow Start
Khi mới connect, không biết network nhanh thế nào. Bắt đầu cwnd = 1 MSS (Maximum Segment Size, ~1460 byte). Mỗi RTT mà không loss, nhân đôi cwnd. "Slow start" — tên gọi sai vì thực ra tăng theo cấp số nhân, nhưng đó là so với "gửi tối đa ngay".
Congestion Avoidance — AIMD
Khi cwnd đạt ngưỡng ssthresh, chuyển sang AIMD (Additive Increase, Multiplicative Decrease):
- Mỗi RTT không loss: cwnd += 1 MSS (cộng dần)
- Khi loss: cwnd /= 2 (giảm một nửa) — "back off"
Các thuật toán congestion control
- Tahoe (1988): loss → cwnd = 1 (slow start lại từ đầu)
- Reno (1990): 3 dup ACK = "fast retransmit", cwnd /= 2 (fast recovery)
- NewReno, SACK
- Cubic (default Linux): tăng theo cubic function — ổn định trên link tốc độ cao
- BBR (Google): model bandwidth + RTT thay vì dựa loss
# Linux: xem congestion control hiện dùng
sysctl net.ipv4.tcp_congestion_control
# net.ipv4.tcp_congestion_control = cubic
# Đổi sang BBR
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
3.8 TCP States — bảng trạng thái
| State | Ý nghĩa |
|---|---|
LISTEN | Server đang chờ connection |
SYN_SENT | Client đã gửi SYN, đợi SYN-ACK |
SYN_RCVD | Server đã nhận SYN, gửi SYN-ACK, đợi ACK cuối |
ESTABLISHED | Đang truyền data |
FIN_WAIT_1 | Đã gửi FIN, đợi ACK của bên kia |
FIN_WAIT_2 | Đã nhận ACK, đợi FIN của bên kia |
CLOSE_WAIT | Nhận FIN từ bên kia, app phải close |
LAST_ACK | Đã gửi FIN, đợi ACK cuối |
TIME_WAIT | Connection đã đóng từ phía mình, đợi 2*MSL trước khi cleanup |
CLOSED | Connection đã giải phóng |
# Quan sát TCP states
ss -tan
# State Recv-Q Send-Q Local Address:Port Peer Address:Port
# LISTEN 0 128 0.0.0.0:80 0.0.0.0:*
# ESTABLISHED 0 0 192.168.1.5:54321 142.250.74.14:443
# TIME_WAIT 0 0 192.168.1.5:54322 ...:443
3.9 TIME_WAIT — bug nổi tiếng của server bận
Sau khi đóng connection, bên active close (gửi FIN trước) phải vào TIME_WAIT trong 2*MSL (Maximum Segment Lifetime, thường ~60s).
Tại sao cần TIME_WAIT?
- Đảm bảo ACK cuối cùng đến được peer: nếu mất, peer retransmit FIN → mình cần còn alive để re-ACK
- Tránh "ghost packet" của connection cũ đụng connection mới có cùng (src,dst,port,port)
Bug: server reverse proxy / API gateway
nginx/HAProxy là "client" với upstream backend. Mỗi request → tạo TCP connect đến backend. Nếu connect xong xuống → nginx active close → vào TIME_WAIT.
Server bận: 10K req/s × 60s TIME_WAIT = 600K connection trong TIME_WAIT. Mỗi connection chiếm 1 ephemeral port. Linux có ~28K ephemeral port → port exhaustion → kết nối mới fail!
Cách giảm TIME_WAIT
# Reuse TIME_WAIT socket
sysctl -w net.ipv4.tcp_tw_reuse=1
# Tăng range ephemeral port
sysctl -w net.ipv4.ip_local_port_range="10000 65535"
# Tăng số connection backlog
sysctl -w net.core.somaxconn=65535
# Cách tốt nhất: dùng KEEP-ALIVE — tái sử dụng connection
# nginx config:
keepalive_requests 1000;
keepalive_timeout 60s;
Bài học: dùng connection pooling / keep-alive thay vì mở/đóng connection liên tục.
3.10 Nagle's Algorithm & Keep-alive
Nagle's Algorithm
Vấn đề: gõ telnet 1 ký tự = 1 packet với 40 byte header + 1 byte data = 97% overhead. Nagle giải: không gửi nếu còn data chưa được ACK, gom nhiều ký tự thành 1 packet.
Tắt Nagle khi cần latency thấp (real-time game, RPC) bằng TCP_NODELAY socket option.
Keep-alive
Sau 1 thời gian không có data, OS gửi probe packet "Bạn còn alive không?". Nếu không reply → đóng connection (zombie cleanup).
# Linux
sysctl net.ipv4.tcp_keepalive_time # 7200s default (2 giờ!)
sysctl net.ipv4.tcp_keepalive_intvl
sysctl net.ipv4.tcp_keepalive_probes
HTTP keep-alive (HTTP/1.1 default) khác: là tái sử dụng TCP connection cho nhiều request, tránh tốn 3-way handshake mỗi request.
Bài tập
Mở Wireshark, filter tcp.flags.syn==1. Browse 1 trang web. Quan sát SYN, SYN-ACK, ACK trên Wireshark.
Chạy server nc -l 8080. Trong terminal khác nc localhost 8080. Mở terminal 3 chạy ss -tan | grep 8080. Quan sát LISTEN, ESTABLISHED. Đóng client (Ctrl+C) → quan sát TIME_WAIT.
Trên server bận: ss -tan | grep TIME-WAIT | wc -l. Bao nhiêu? Tại sao cao?
Giải thích vì sao 2-way handshake không đủ. Kịch bản nào fail?
Trên Linux: tcpdump -ttt -i any 'host google.com' trong khi curl https://google.com -o /dev/null. Quan sát growth của window/cwnd qua các packet.
Khi nào nên TCP_NODELAY? Cho 3 use case cụ thể (vd realtime game, API call, telnet).
Chạy curl -v https://example.com -v https://example.com. Có thể thấy connection được tái sử dụng không? Thêm --http1.1. Quan sát Connection: header.
🧪 Quiz cuối chương
Câu 1. 3-way handshake gồm các packet?
Đáp án: SYN → SYN-ACK → ACK. Mất 1.5 RTT. SYN-ACK = 1 packet với cả SYN flag và ACK flag.
Câu 2. Tại sao cần 3 packet, không phải 2?
Đáp án: confirm 2 chiều. 2-way chỉ confirm 1 chiều — server không biết client nhận được seq mình.
Câu 3. TIME_WAIT tồn tại để?
Đáp án: ACK cuối + ghost packet. 2*MSL ~60s — đủ để mọi packet cũ "chết" trong network.
Câu 4. Sliding Window dùng cho?
Đáp án: flow control. Tránh sender ngập receiver.
Câu 5. AIMD trong congestion control?
Đáp án: AIMD. Tăng dần, khi mất giảm mạnh (back off). Behavior này tạo nên "saw-tooth" graph.
Câu 6. ACK number = N nghĩa là?
Đáp án: byte tiếp theo mong nhận = N. ACK là "cumulative" — xác nhận tất cả byte trước.
Câu 7. Khi server có quá nhiều TIME_WAIT, hệ quả?
Đáp án: port exhaustion. Linux có ~28K ephemeral port. Server bận với short connections → kẹt. Fix: keep-alive, connection pooling.
Câu 8. TCP_NODELAY tắt cái gì?
Đáp án: tắt Nagle. Cho realtime game, RPC — cần latency thấp hơn throughput.
Tổng kết chương 3
- ✅ TCP = connection-oriented, reliable, in-order, có flow + congestion control
- ✅ 3-way handshake: SYN → SYN-ACK → ACK (1.5 RTT)
- ✅ 4-way termination: FIN, ACK, FIN, ACK
- ✅ Sequence # đánh số byte; ACK # = byte tiếp theo mong nhận; cumulative
- ✅ Sliding Window = flow control (không ngập receiver)
- ✅ Congestion control: slow start (×2 mỗi RTT) → AIMD (+1 / ÷2)
- ✅ Linux dùng Cubic mặc định; Google đẩy mạnh BBR
- ✅ TIME_WAIT 2*MSL ~60s — bug port exhaustion ở proxy/gateway bận
- ✅ Fix: keep-alive, connection pooling,
tcp_tw_reuse - ✅ Tools:
ss -tan, Wireshark,tcpdump