TCP và UDP: Cổng và Kết Nối
Tầng mạng (IP) đưa gói tin tới đúng máy. Nhưng một máy chạy nhiều dịch vụ cùng lúc (web, ssh, database). Làm sao biết gói tin dành cho dịch vụ nào, và làm sao đảm bảo dữ liệu tới nơi đầy đủ, đúng thứ tự? Đó là việc của tầng giao vận (tầng 4) — với hai giao thức chính: TCP và UDP, cùng khái niệm cổng (port).
Cổng: định danh dịch vụ trên một máy
Một cổng là số 16 bit (0–65535) định danh một dịch vụ/kết nối cụ thể trên một máy. IP đưa gói tới đúng máy; cổng đưa nó tới đúng chương trình trên máy đó.
Cổng chia làm ba dải:
0 – 1023 Well-known (cần quyền cao để mở) — dịch vụ chuẩn
1024 – 49151 Registered — gán cho ứng dụng cụ thể
49152– 65535 Ephemeral (tạm) — client tự lấy khi mở kết nối
Vài cổng well-known nên thuộc (theo IANA):
22 SSH 53 DNS 443 HTTPS
80 HTTP 25 SMTP 3306 MySQL
123 NTP 53 DNS 5432 PostgreSQL
Khi bạn vào https://example.com, ngầm hiểu là cổng 443; http:// là 80. Server "lắng nghe" trên cổng cố định của nó; client thì dùng một cổng tạm (ephemeral) cho mỗi kết nối.
Một kết nối được định danh bằng 4 thông tin
Đây là ý quan trọng. Một kết nối TCP được xác định duy nhất bởi bốn thứ (4-tuple): IP nguồn, cổng nguồn, IP đích, cổng đích. Xem kết nối thật trên máy bạn:
netstat -an -p tcp | grep ESTABLISHED
192.168.71.168.55696 → 34.149.66.137.443 ESTABLISHED
└──IP nguồn──┘ └port┘ └──IP đích──┘ └port┘
Cùng máy bạn (192.168.71.168) có thể mở nhiều kết nối tới cùng một server cổng 443, miễn cổng nguồn khác nhau (55696, 55579...). Nhờ 4-tuple này mà máy phân biệt được hàng trăm kết nối song song — và cũng là cách NAT (Bài 5) dùng cổng để theo dõi từng kết nối.
TCP: kết nối tin cậy
TCP (Transmission Control Protocol) cung cấp một kết nối tin cậy, đúng thứ tự: dữ liệu tới đầy đủ, đúng trình tự, không trùng. Nó làm được nhờ đánh số từng byte, xác nhận (ACK) cái đã nhận, và gửi lại cái bị mất. (Chuẩn hiện hành là RFC 9293, ra 2022, thay cho RFC 793 kinh điển.)
Trước khi truyền dữ liệu, TCP thiết lập kết nối bằng bắt tay ba bước (3-way handshake):
Client Server
│ ──── SYN (seq=x) ──────────────────► │ "Tôi muốn kết nối"
│ ◄─── SYN-ACK (seq=y, ack=x+1) ─────── │ "OK, tôi cũng sẵn sàng"
│ ──── ACK (ack=y+1) ────────────────► │ "Xác nhận, bắt đầu thôi"
│ ═════════ kết nối ESTABLISHED ═══════ │
│ ◄────────── dữ liệu hai chiều ──────► │
Ba bước này (SYN → SYN-ACK → ACK) cho hai bên đồng bộ và xác nhận cả hai sẵn sàng. (Bạn có thể tự thấy chúng bằng sudo tcpdump -n 'tcp port 443' khi mở một trang web.) Khi xong việc, kết nối được đóng lại lịch sự bằng trao đổi gói FIN.
Cái giá của độ tin cậy: handshake tốn một vòng đi-về (round trip) trước khi gửi được dữ liệu — thêm độ trễ. Đây là một lý do người ta tối ưu (giữ kết nối, HTTP/2, QUIC — Bài 8).
Trạng thái kết nối TCP
TCP là giao thức "có trạng thái" — mỗi kết nối đi qua các trạng thái. Xem thống kê trên máy bạn:
netstat -an -p tcp | awk 'NR>2{print $NF}' | sort | uniq -c | sort -rn
37 ESTABLISHED ← đang truyền dữ liệu
20 LISTEN ← server đang chờ kết nối tới
1 TIME_WAIT ← vừa đóng, chờ một lúc cho chắc
Vài trạng thái cần biết khi gỡ lỗi:
- LISTEN — một dịch vụ đang chờ kết nối ở cổng đó (nhớ
ss -tlnpở Bài 13 series Linux). Không có LISTEN = không ai phục vụ cổng đó. - ESTABLISHED — kết nối đang hoạt động.
- TIME_WAIT — kết nối vừa đóng, hệ thống giữ lại một lúc để xử lý gói đến muộn. Nhiều TIME_WAIT là bình thường; rất nhiều có thể là dấu hiệu mở/đóng kết nối quá nhiều.
- SYN_SENT / SYN_RECV — đang trong quá trình bắt tay. Kẹt ở SYN_SENT thường nghĩa là không tới được server (firewall chặn, server không nghe).
UDP: nhanh, không bảo đảm
UDP (User Datagram Protocol) là phương án ngược lại: không kết nối, không bảo đảm. Nó cứ gửi gói đi (datagram), không bắt tay, không xác nhận, không gửi lại nếu mất. Đổi lại: nhanh, ít overhead, độ trễ thấp.
Khi nào dùng UDP thay TCP? Khi tốc độ/độ trễ quan trọng hơn việc nhận đủ 100%:
- DNS (Bài 7) — một câu hỏi/trả lời ngắn, hỏi lại nếu mất còn nhanh hơn bắt tay TCP.
- Video call, game online, streaming — mất một khung hình thì bỏ qua, không ai muốn "tua lại"; trễ mới là kẻ thù.
- QUIC / HTTP/3 — xây trên UDP để tránh độ trễ bắt tay của TCP, tự lo độ tin cậy ở tầng trên.
TCP UDP
─────────────────────────────────────────────
có bắt tay (3 bước) không bắt tay
đảm bảo tới, đúng thứ tự không đảm bảo
gửi lại gói mất mất là mất
chậm hơn (overhead) nhanh, nhẹ
web, ssh, file, database DNS, video, game, QUIC
Kiểm tra cổng bằng nc
Một cổng có mở (có dịch vụ nghe) không? nc (netcat) thử kết nối:
nc -z -G 3 1.1.1.1 443
Connection to 1.1.1.1 port 443 [tcp/https] succeeded!
-z chỉ kiểm tra (không gửi dữ liệu). "succeeded" = cổng mở, có dịch vụ nghe (handshake TCP thành công). Đây là công cụ nhanh để kiểm tra "service có nghe ở cổng đó không / firewall có chặn không" (Bài 12).
Tổng kết
Tầng giao vận đưa dữ liệu tới đúng dịch vụ qua cổng (16 bit; well-known < 1024), và một kết nối được định danh bởi 4-tuple (IP+cổng nguồn/đích). TCP cho kết nối tin cậy, đúng thứ tự qua bắt tay 3 bước (SYN/SYN-ACK/ACK) và trải qua các trạng thái (LISTEN, ESTABLISHED, TIME_WAIT...) — dùng cho web/ssh/database. UDP không kết nối, không bảo đảm nhưng nhanh — dùng cho DNS/video/game. netstat/ss xem kết nối, nc -z kiểm tra cổng.
DNS dùng UDP và là chặng đầu của mọi request (Bài 0). Bài 7 đào vào nó: tên miền được phân giải thành IP ra sao.