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(chỉ an toàn cho outbound và cần timestamp, xem bài 08), 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
│ (SYN (seq=x))
▼
ServerMỗ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 (A) Bên kia (B)
│ │
│ ──────── FIN ────────────────► │
│ ◄─────── ACK ───────────────── │
│ ◄─────── FIN ───────────────── │
│ ──────── 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.
Nagle + delayed ACK, lỗi latency ẩn
Nagle algorithm: kernel gộp các ghi nhỏ vào một segment lớn, chờ ACK trước khi gửi tiếp, giảm overhead nhưng tăng latency.
Delayed ACK: bên nhận chờ ~40–200ms trước khi gửi ACK, hy vọng gửi kèm data (piggyback).
Khi cả hai cùng bật (mặc định):
Sender: gửi data nhỏ → Nagle chờ ACK
Receiver: delayed ACK chờ 40ms
→ mỗi gói nhỏ bị trễ 40ms!
Triệu chứng: request/response nhỏ (đặc biệt protocol tự định nghĩa, Redis pipeline, game) có latency spike ~40ms không giải thích được.
Giải pháp: bật TCP_NODELAY (tắt Nagle) cho ứng dụng latency-sensitive:
int flag = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
Hầu hết HTTP library và database driver đã bật TCP_NODELAY mặc định. Vấn đề thường xuất hiện với custom protocol hoặc library cũ.
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 │ │ Connection timed out │
├───────────────────────────┤ ├─────────────────────────────┤
│ - Server gửi RST ngay │ │ - Client không nhận phản hồi│
│ - Không có process LISTEN │ │ trong timeout │
│ trên cổng đó (hoặc │ │ - Firewall DROP gói SYN, │
│ firewall REJECT) │ │ routing sai, SYN backlog │
│ │ │ đầy (server quá tải) │
└───────────────────────────┘ └─────────────────────────────┘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 (segments, thứ tự, tái truyền)
│
▼
Server (ứng dụng đọc byte stream)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.
- Nagle + delayed ACK gây latency ẩn với gói nhỏ, dùng
TCP_NODELAYcho app latency-sensitive. - 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.