TCP và UDP: Cổng và Kết Nối

K
Kai··5 min read

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: TCPUDP, 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://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.