Sau bài này bạn làm được: giải thích tại sao connection refused khác connection timed out; đọc output ss -lntp và biết process nào đang giữ cổng nào; hiểu vì sao HTTP “ngồi” trên TCP.
Socket = IP + cổng + giao thức
Ứng dụng không kết nối tới “một máy” — nó mở một socket xác định bởi bộ tứ:
(source IP, source port, destination IP, destination port)
Ví dụ: curl https://api.example.com sẽ mở socket kiểu:
(10.0.1.5, 54312, 93.184.216.34, 443)
- Cổng nguồn (ephemeral): kernel tự chọn trong dải 32768–60999 (Linux mặc định, xem
sysctl net.ipv4.ip_local_port_range). - Cổng đích: do giao thức quy định (443 = HTTPS, 80 = HTTP, 22 = SSH…).
- Giao thức: TCP hoặc UDP.
Port exhaustion: một process mở rất nhiều outbound connection tới cùng một (dst IP, dst port) có thể cạn dải ephemeral →
EADDRNOTAVAIL. Hay gặp ở proxy, crawler, benchmark tool. Bốn hướng khắc phục: mở rộngip_local_port_range, bậttcp_tw_reuse, phân tán đích qua nhiều IP, hoặc HTTP keep-alive để tái dùng connection.
Cổng và trạng thái LISTEN
Process server phải gọi bind() + listen() trên một cổng trước khi nhận kết nối. Lệnh kiểm tra:
ss -lntp
# -l listen, -n không resolve tên, -t TCP, -p process
# Output mẫu:
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1234))
LISTEN 0 128 127.0.0.1:3000 0.0.0.0:* users:(("node",pid=5678))
0.0.0.0:443→ lắng nghe mọi interface.127.0.0.1:3000→ chỉ lắng nghe loopback (không truy cập được từ ngoài!).
TCP — giao thức tin cậy, có trạng thái
3-way handshake (kết nối)
Client Server
│ │
│──── SYN (seq=x) ─────────────▶│
│ │ Server nhớ: "có client đang kết nối"
│◀─── SYN-ACK (seq=y, ack=x+1)──│
│ │
│──── ACK (ack=y+1) ───────────▶│
│ │
│═══════ Data flow ══════════════│
Mỗi vòng SYN→SYN-ACK→ACK tốn 1 RTT trước khi byte đầu tiên của data được gửi. Đây là lý do TLS 1.3 và HTTP/3 tích cực giảm số round-trip.
TCP đảm bảo gì
- Thứ tự: dữ liệu đến đúng thứ tự gửi (sequence number).
- Không mất mát: có ACK, mất thì gửi lại.
- Điều khiển tắc nghẽn: không flood mạng (window, slow start, CUBIC…).
Graceful close (4-way)
Bên muốn đóng: FIN ──▶ Bên kia: ACK
Bên kia: FIN ──▶ Bên muốn đóng: ACK
TIME_WAIT ở phía chủ động đóng kéo dài ~60–120s để đảm bảo không còn gói cũ trên mạng.
UDP — đơn giản, không đảm bảo
UDP gửi datagram không cần handshake, không ACK, không đảm bảo thứ tự hay không mất gói. Ứng dụng tự xử lý nếu cần độ tin cậy.
| Tính chất | TCP | UDP |
|---|---|---|
| Kết nối | Có (stateful) | Không (stateless) |
| Đảm bảo giao | Có (retransmit) | Không |
| Thứ tự | Có | Không |
| Overhead | Cao hơn | Thấp hơn |
| Dùng cho | HTTP, HTTPS, SSH, DB | DNS, QUIC, game, streaming |
“Connection refused” vs “Connection timed out”
Đây là hai lỗi hay nhầm nhưng nguyên nhân rất khác:
Connection refused
└── Server gửi RST ngay lập tức
└── Nguyên nhân: không có process LISTEN trên cổng đó
(hoặc firewall REJECT — ít gặp hơn)
Connection timed out
└── Client không nhận được phản hồi trong timeout
└── Nguyên nhân: firewall DROP gói SYN
routing sai (gói không đến nơi)
server quá tải, SYN backlog đầy
Kinh nghiệm: refused → kiểm tra process và cổng; timeout → kiểm tra firewall/SG/routing.
TCP và HTTP — tại sao HTTP “ngồi” trên TCP
HTTP cần dữ liệu đúng thứ tự, không mất mát (hãy tưởng tượng HTML bị mất nửa chừng). TCP cung cấp byte stream tin cậy — HTTP library chỉ cần đọc stream và parse.
[HTTP Request text] ───▶ [TCP: chia thành segments, đảm bảo thứ tự] ───▶ [Server]
HTTP/3 dùng QUIC (trên UDP) nhưng QUIC tự cài đặt cơ chế tin cậy ở tầng ứng dụng — sẽ đề cập bài 05.
Thực hành: kiểm tra kết nối TCP
# Kiểm tra cổng có mở không (không gửi dữ liệu HTTP)
nc -vz api.example.com 443
# -v verbose, -z zero-I/O (chỉ test kết nối)
# Xem tất cả kết nối TCP đang ESTABLISHED
ss -tnp state established
# Đếm số kết nối theo trạng thái
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn
Tóm tắt
- Socket = bộ tứ (src IP, src port, dst IP, dst port) — định danh duy nhất một kết nối.
- TCP đảm bảo thứ tự và không mất gói; UDP không đảm bảo nhưng nhanh và nhẹ hơn.
- Phân biệt refused (không có ai nghe) vs timeout (firewall/routing).
ss -lntplà lệnh đầu tiên khi nghi ngờ “service có đang chạy không”.
Câu hỏi hay gặp
curl báo Connection refused tới localhost:8080 — nên làm gì tiếp?
Trả lời: Chạy ss -lntp (hoặc lsof -i :8080) xem có process LISTEN trên 8080 không. Refused = không có socket đang nghe đúng IP:port (hoặc chỉ bind 127.0.0.1 trong khi bạn gọi sai interface). Không phải firewall DROP (thường là timeout).
TIME_WAIT để làm gì? Nhiều TIME_WAIT quá thì sao?
Trả lời: TIME_WAIT giúp tránh gói cũ lạc vào kết nối mới cùng bộ tứ socket. Quá nhiều có thể cạn cổng ephemeral hoặc tốn tài nguyên kernel — cần xem tần suất đóng kết nối, ip_local_port_range, và có reuse phù hợp khi thiết kế cho phép.
Game online thường dùng UDP thay TCP — vì sao, đánh đổi gì?
Trả lời: UDP không chờ ACK / không head-of-line ở tầng TCP, phù hợp cập nhật vị trí realtime; độ trễ thấp hơn khi chấp nhận mất gói / thứ tự không đảm bảo. Trade-off: ứng dụng (hoặc layer trên) tự xử lý reliability nếu cần.
Bài tiếp theo (Giai đoạn I): DNS thực dụng cho developer — sau khi có TCP tới đúng IP, DNS là bước trước đó mà mọi request đều phải qua.