Bối Cảnh & Lý Do: Hai Giao Thức Giải Quyết Các Vấn Đề Khác Nhau
Mọi gói tin ứng dụng của bạn gửi đi đều đi qua TCP hoặc UDP. Lựa chọn đó — và việc liệu nó có chủ đích hay không — quyết định cách service của bạn xử lý tải, kết nối không ổn định và gói tin bị mất. Khi bạn đang truy tìm nguyên nhân latency tăng đột biến lúc 2 giờ sáng, biết giao thức nào đang có vấn đề (và cách xác minh trực tiếp trên đường truyền) sẽ giúp bạn tìm ra nguyên nhân gốc rễ nhanh hơn bất kỳ dashboard nào.
Cả TCP lẫn UDP đều hoạt động ở Tầng 4 (Transport Layer) của mô hình OSI, chạy bên trên IP. Tuy nhiên, mục tiêu thiết kế của chúng lại đi theo hai hướng đối lập.
TCP — Được Xây Dựng Cho Độ Tin Cậy
TCP (Transmission Control Protocol) thiết lập kết nối trước khi truyền bất kỳ dữ liệu nào, thông qua quá trình bắt tay 3 bước:
Client → Server: SYN
Server → Client: SYN-ACK
Client → Server: ACK
(kết nối được thiết lập, dữ liệu bắt đầu truyền)
Từ thời điểm đó, TCP đảm bảo các gói tin đến nơi, đến đúng thứ tự, và người gửi được thông báo về bất kỳ gói nào bị mất. Cơ chế truyền lại, kiểm soát luồng và kiểm soát tắc nghẽn đều được tích hợp sẵn. Độ tin cậy đó có cái giá thực sự: header TCP chiếm 20–60 byte, và mỗi kết nối mới tiêu tốn ít nhất 1.5 RTT trước khi byte dữ liệu đầu tiên đến nơi. SSH, HTTP/HTTPS, PostgreSQL, MySQL — chúng chấp nhận overhead này vì tính toàn vẹn dữ liệu là bắt buộc.
UDP — Được Xây Dựng Cho Tốc Độ
UDP (User Datagram Protocol) bỏ qua tất cả những thứ đó. Không bắt tay, không xác nhận, không truyền lại. Bạn nhét gói tin vào datagram, bắn đi rồi tiếp tục. Nếu nó bị mất, giao thức không quan tâm — đó là vấn đề ứng dụng của bạn phải tự xử lý (hoặc bỏ qua).
Lợi ích: header của UDP chỉ vỏn vẹn 8 byte, và không có overhead thiết lập kết nối. DNS, VoIP, video streaming, game online, NTP — chúng dùng UDP vì một gói tin bị mất còn đỡ đau hơn nhiều so với việc retry 200ms khiến trải nghiệm người dùng bị đóng băng.
So sánh trực tiếp, đây là những điểm thực sự quan trọng trong thực tế:
- Kết nối: TCP yêu cầu bắt tay; UDP không kết nối
- Độ tin cậy: TCP truyền lại gói tin bị mất; UDP thì không
- Thứ tự: TCP phân phối theo trình tự; UDP có thể đến không theo thứ tự
- Tốc độ: UDP có ít overhead hơn và latency thấp hơn
- Trường hợp dùng: TCP cho tính toàn vẹn dữ liệu; UDP cho hiệu năng thời gian thực
Cài Đặt: Thiết Lập Bộ Công Cụ Kiểm Tra
Trước khi thực hành, bạn cần một số công cụ. Trên Debian/Ubuntu:
sudo apt update
sudo apt install -y netcat-openbsd tcpdump iproute2 python3
Trên RHEL/CentOS/Fedora:
sudo dnf install -y nmap-ncat tcpdump iproute python3
Kiểm tra mọi thứ đã sẵn sàng:
nc --version
tcpdump --version
ss --version
netcat (nc) là công cụ kiểm tra chính của bạn — nó mở kết nối TCP hoặc UDP trực tiếp từ shell chỉ với một flag khác biệt. tcpdump bắt gói tin trực tiếp để bạn thấy header giao thức thực tế trên đường truyền. ss (socket statistics) đã thay thế netstat từ nhiều năm trước và nhanh hơn đáng kể trên các hệ thống bận.
Thực Hành: Làm Việc với TCP và UDP
Kiểm Tra TCP với Netcat
Mở một TCP listener trên cổng 9000 trong một terminal:
nc -l -p 9000
Kết nối từ terminal khác (hoặc máy khác):
nc 127.0.0.1 9000
Nhập bất cứ thứ gì — nó sẽ xuất hiện ở phía listener. Phía sau hậu trường, TCP đã thương lượng kết nối trước. Kết thúc client bằng Ctrl+C và server nhận được EOF. Đó là quá trình đóng kết nối uyển chuyển của TCP (chuỗi FIN/FIN-ACK).
Kiểm Tra UDP với Netcat
Thêm flag -u cho UDP:
# Bên lắng nghe
nc -u -l -p 9001
# Bên gửi (terminal khác)
echo "xin chào qua udp" | nc -u 127.0.0.1 9001
Không cần thiết lập kết nối. Gói tin hoặc đến hoặc không. Kết thúc listener và tiếp tục gửi từ client — bạn sẽ không nhận được lỗi nào. UDP không có cách nào biết phía kia đã ngắt kết nối.
Lập Trình Socket: TCP vs UDP trong Python
Ở cấp độ API, sự khác biệt chỉ là một hằng số. Một TCP server tối giản:
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: # SOCK_STREAM = TCP
s.bind(('0.0.0.0', 9000))
s.listen(1)
conn, addr = s.accept() # chặn đến khi client kết nối
with conn:
data = conn.recv(1024)
print(f"Nhận được: {data.decode()}")
conn.sendall(b"ACK")
Tương đương UDP — không có accept(), không có trạng thái kết nối:
import socket
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: # SOCK_DGRAM = UDP
s.bind(('0.0.0.0', 9001))
data, addr = s.recvfrom(1024) # không bắt tay, chỉ chờ datagram
print(f"Từ {addr}: {data.decode()}")
s.sendto(b"\u0111\u00e3 nh\u1eadn", addr)
SOCK_STREAM vs SOCK_DGRAM — đó là toàn bộ quyết định ở cấp độ API. Mọi thứ khác (độ tin cậy, thứ tự, truyền lại) đều tự động theo sau từ một flag đó.
Chọn Giao Thức Phù Hợp Cho Ứng Dụng của Bạn
Quy tắc đơn giản: nếu mất một gói tin làm hỏng trải nghiệm người dùng hoặc làm hư dữ liệu, dùng TCP. Nếu một gói tin bị mất chỉ là lỗi nhỏ — hoặc được thay thế bởi bản cập nhật tiếp theo chỉ một phần nghìn giây sau — UDP thắng.
Các trường hợp cụ thể:
- REST API / truy vấn database: TCP — bạn cần toàn bộ phản hồi, theo đúng thứ tự
- Tra cứu DNS: UDP — các truy vấn thông thường dưới 512 byte và phân giải trong <50ms; TCP fallback được dùng khi phản hồi vượt quá 4096 byte (EDNS)
- Cuộc gọi video: UDP — ở 30fps, một khung hình bị mất hầu như không đáng chú ý; một lần retry 500ms của TCP đóng băng toàn bộ cuộc gọi
- Truyền file (SCP, rsync): TCP — mọi byte phải đến nguyên vẹn
- Đồng bộ trạng thái game: UDP — cập nhật vị trí sẽ bị thay thế bởi gói tin tiếp theo dù sao
- Gửi log (syslog): UDP phổ biến; mất một dòng log thỉnh thoảng thường có thể chấp nhận được
Xác Minh & Giám Sát: Theo Dõi TCP và UDP Hoạt Động
Liệt Kê Kết Nối Đang Hoạt Động với ss
Xem tất cả kết nối TCP hiện tại trên hệ thống:
ss -tnp
UDP socket (lưu ý: UDP không có khái niệm ESTABLISHED — bạn sẽ thấy UNCONN cho các socket chưa kết nối):
ss -unp
Lọc theo cổng cụ thể:
ss -tnp sport = :443
ss -unp dport = :53
Bắt Gói Tin với tcpdump
Bắt gói tin TCP trên cổng 9000:
sudo tcpdump -i lo tcp port 9000 -v
Bạn sẽ thấy quá trình bắt tay 3 bước rõ ràng — SYN, SYN-ACK, ACK — trước bất kỳ dữ liệu nào. Với UDP:
sudo tcpdump -i lo udp port 9001 -v
Gói tin đầu tiên đã là dữ liệu. Không thiết lập, không thương lượng. Đó là sự khác biệt cốt lõi được thể hiện trực tiếp ở cấp độ đường truyền.
Kiểm Tra Trạng Thái TCP với netstat (Cũ)
Nếu ss không khả dụng:
netstat -tnp | grep ESTABLISHED
netstat -unp
Theo Dõi TCP Retransmission
Tỷ lệ retransmission cao trong môi trường production thường có nghĩa là có tắc nghẽn mạng hoặc mất gói tin ở đâu đó trên đường đi. Kiểm tra bằng:
ss -s
# hoặc
cat /proc/net/snmp | grep -i retrans
Retransmission tăng dần trên một service nhạy cảm với latency đáng để truy tìm. Hoặc theo dõi tắc nghẽn đến tận nguồn, hoặc đặt câu hỏi liệu luồng traffic đó có thực sự cần TCP hay không.
TCP và UDP đã là một phần của BSD socket API kể từ 4.2BSD năm 1983 — hơn 40 năm, và vẫn là nền tảng mà mọi network stack xây dựng trên đó. Những kỹ sư có thể lựa chọn đúng giao thức không chần chừ, và xác minh giả định của mình bằng tcpdump thay vì đoán mò, liên tục gỡ lỗi vấn đề production nhanh hơn và đưa ra quyết định sắc bén hơn khi độ tin cậy và latency kéo theo hai hướng đối lập.

