TCP 與 UDP:傳輸層協定深入解析 | Networking
TCP 與 UDP 是傳輸層(Transport Layer)最核心的兩個協定。TCP 提供可靠、有序、連接導向的資料傳輸,適用於網頁瀏覽、檔案傳輸等場景;UDP 則以無連接、低延遲的方式快速傳送資料,適合即時語音、線上遊戲等應用。本文從 封包結構、三向交握、流量控制、壅塞控制 到 Socket 程式設計 與 診斷指令,完整解析兩大傳輸層協定的運作原理與實務應用。
傳輸層在網路模型中的角色
傳輸層(Transport Layer)位於 OSI 模型的第四層,介於應用層與網路層之間,負責端對端(End-to-End)的資料傳輸。應用程式透過傳輸層將資料交給底層網路傳送,而傳輸層則透過 端口號(Port Number) 來區分同一主機上的不同應用程式。
┌─────────────────────────────────┐
│ 應用層(Application Layer) │ HTTP, FTP, DNS, SMTP, SSH
├─────────────────────────────────┤
│ 傳輸層(Transport Layer) │ ← TCP / UDP 在此層
├─────────────────────────────────┤
│ 網路層(Network Layer) │ IP, ICMP, ARP
├─────────────────────────────────┤
│ 資料鏈結層(Data Link Layer) │ Ethernet, Wi-Fi
├─────────────────────────────────┤
│ 實體層(Physical Layer) │ 電纜、光纖、無線電波
└─────────────────────────────────┘
端口號的範圍從 0 到 65535,分為三個區段:
| 範圍 | 名稱 | 說明 |
|---|---|---|
| 0 – 1023 | Well-known Ports | 系統保留,需 root 權限(HTTP 80、HTTPS 443、SSH 22) |
| 1024 – 49151 | Registered Ports | IANA 註冊的應用程式端口(MySQL 3306、Redis 6379) |
| 49152 – 65535 | Dynamic / Ephemeral Ports | 客戶端臨時使用,由作業系統自動分配 |
TCP 協定總覽
傳輸控制協定(Transmission Control Protocol,TCP)是一種 連接導向(Connection-oriented) 的傳輸層協定,提供可靠、有序的位元組串流傳輸。在開始傳送資料之前,TCP 必須先透過三向交握(Three-way Handshake)建立連線,確保通訊雙方準備就緒。
TCP 的核心特性:
- 可靠傳輸(Reliable):透過序號、確認應答、重傳機制保證資料完整到達
- 有序傳輸(Ordered):接收端按序號重組資料,保證順序正確
- 流量控制(Flow Control):滑動窗口機制防止發送方淹沒接收方
- 壅塞控制(Congestion Control):偵測網路壅塞並動態調整傳輸速率
- 全雙工(Full-duplex):雙方可同時傳送與接收資料
TCP 標頭結構
TCP 標頭的基本大小為 20 bytes,加上可選項(Options)最多可達 60 bytes。以下是標頭的主要欄位:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |C|E|U|A|P|R|S|F| |
| Offset| Rsrvd |W|C|R|C|S|S|Y|I| Window |
| | |R|E|G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 欄位 | 大小 | 說明 |
|---|---|---|
| Source Port | 16 bits | 來源端口號 |
| Destination Port | 16 bits | 目的端口號 |
| Sequence Number | 32 bits | 資料位元組的序號,用於排序與重組 |
| Acknowledgment Number | 32 bits | 期望接收的下一個位元組序號 |
| Data Offset | 4 bits | 標頭長度(以 4 bytes 為單位) |
| Flags | 9 bits | 控制旗標:SYN、ACK、FIN、RST、PSH、URG 等 |
| Window | 16 bits | 接收窗口大小,用於流量控制 |
| Checksum | 16 bits | 標頭與資料的校驗和 |
其中 Flags 旗標欄位是 TCP 連線管理的關鍵:
| 旗標 | 全名 | 用途 |
|---|---|---|
| SYN | Synchronize | 建立連線,同步初始序號 |
| ACK | Acknowledgment | 確認收到資料 |
| FIN | Finish | 請求關閉連線 |
| RST | Reset | 強制重置連線 |
| PSH | Push | 要求立即將資料推送給應用層 |
| URG | Urgent | 標記緊急資料 |
TCP 三向交握(Three-way Handshake)
TCP 連線建立需要三次握手,目的是同步雙方的初始序號(Initial Sequence Number,ISN)並確認雙方均準備好通訊。
客戶端 (Client) 伺服器 (Server)
│ │
│ ① SYN (seq=x) │
│ ──────────────────────────────────→│
│ 客戶端進入 SYN_SENT 狀態 │ 伺服器進入 SYN_RCVD 狀態
│ │
│ ② SYN-ACK (seq=y, ack=x+1) │
│←────────────────────────────────── │
│ │
│ ③ ACK (seq=x+1, ack=y+1) │
│ ──────────────────────────────────→│
│ 連線建立(ESTABLISHED) │ 連線建立(ESTABLISHED)
│ │
詳細流程:
- SYN:客戶端發送 SYN 封包,攜帶隨機初始序號
x,進入SYN_SENT狀態 - SYN-ACK:伺服器回應 SYN+ACK,攜帶自己的初始序號
y,確認號為x+1,進入SYN_RCVD狀態 - ACK:客戶端回覆 ACK,確認號為
y+1,雙方進入ESTABLISHED狀態
為什麼需要三次而非兩次? 三次握手確保雙方都能確認對方的接收與發送能力。若只有兩次,伺服器無法確認客戶端已收到 SYN-ACK,可能導致半開連線(Half-open Connection),白白浪費伺服器資源。
TCP 四次揮手(Four-way Termination)
TCP 是全雙工協定,每個方向的資料傳輸需要獨立關閉,因此連線關閉需要四次揮手。
客戶端 (Client) 伺服器 (Server)
│ │
│ ① FIN (seq=u) │
│ ──────────────────────────────────→│
│ FIN_WAIT_1 │ CLOSE_WAIT
│ │
│ ② ACK (ack=u+1) │
│←────────────────────────────────── │
│ FIN_WAIT_2 │ (伺服器可能還有資料要傳送)
│ │
│ ③ FIN (seq=v) │
│←────────────────────────────────── │
│ TIME_WAIT │ LAST_ACK
│ │
│ ④ ACK (ack=v+1) │
│ ──────────────────────────────────→│
│ 等待 2×MSL 後關閉 │ CLOSED
│ CLOSED │
│ │
流程說明:
- FIN:主動關閉方發送 FIN,表示「我不再發送資料了」
- ACK:被動關閉方確認收到 FIN,但此時可能還有資料尚未傳送完畢
- FIN:被動關閉方傳完剩餘資料後,也發送 FIN
- ACK:主動關閉方確認,進入
TIME_WAIT狀態,等待 2 x MSL(Maximum Segment Lifetime,通常 60 秒)後關閉
TIME_WAIT 的意義: 確保最後的 ACK 能到達對方(若遺失,對方會重傳 FIN),同時讓網路中殘留的舊封包自然消亡,避免影響後續新連線。
TCP 滑動窗口與流量控制
滑動窗口(Sliding Window)是 TCP 流量控制的核心機制,允許發送方在未收到確認的情況下連續發送多個封包,大幅提升傳輸效率。
發送方的資料緩衝區:
已確認 ┃ 可發送(窗口內) ┃ 不可發送(窗口外)
──────────┃───────────────────┃────────────────────
1 2 3 4 ┃ 5 6 7 8 9 10 11 ┃ 12 13 14 15 ...
↑ ↑
窗口左邊界 窗口右邊界
運作方式:
- 接收方在 ACK 中通告 Window Size,表示自己剩餘的緩衝區空間
- 基本窗口最大為 65,535 bytes(16 bits),透過 Window Scale 選項可擴展至 1 GB
- 當接收方緩衝區滿時,通告窗口大小為 0,發送方暫停傳送
- 接收方處理完資料後,發送 Window Update 通知發送方繼續
這個機制確保快速的發送方不會淹沒較慢的接收方,實現端對端的流量調節。
TCP 壅塞控制
壅塞控制(Congestion Control)與流量控制不同——流量控制防止發送方淹沒「接收方」,壅塞控制則防止發送方淹沒「網路」。TCP 使用壅塞窗口(Congestion Window,cwnd)來動態調整傳輸速率。
cwnd (壅塞窗口大小)
│
│ ╱ 壅塞避免(線性增加)
│ ╱
│ ╱ ← ssthresh(慢啟動閾值)
│ ╱
│ ╱ 慢啟動(指數增加)
│ ╱
│╱
└──────────────────────────── 時間
↑
發生壅塞 → cwnd 減半或重置
| 演算法 | 觸發條件 | 行為 |
|---|---|---|
| 慢啟動(Slow Start) | 連線初始或超時後 | cwnd 從 1 MSS 開始,每收到一個 ACK 翻倍(指數增長) |
| 壅塞避免(Congestion Avoidance) | cwnd >= ssthresh | cwnd 每個 RTT 增加 1 MSS(線性增長) |
| 快速重傳(Fast Retransmit) | 收到 3 個重複 ACK | 不等超時,立即重傳遺失的封包 |
| 快速恢復(Fast Recovery) | 快速重傳後 | ssthresh = cwnd/2,cwnd = ssthresh + 3 MSS |
常見的壅塞控制演算法包括經典的 Reno(AIMD 策略)、Linux 預設的 Cubic(三次方函數控制窗口增長),以及 Google 開發的 BBR(基於頻寬與延遲模型)。
UDP 協定總覽
使用者資料報協定(User Datagram Protocol,UDP)是一種 無連接(Connectionless) 的傳輸層協定。與 TCP 不同,UDP 不需要建立連線、不保證資料到達、也不保證順序——它只負責把資料盡快送出去。
UDP 的核心特性:
- 無連接:不需要握手,直接發送資料報(Datagram)
- 不保證可靠性:封包可能遺失、亂序或重複,不提供重傳機制
- 低延遲:沒有握手延遲、沒有壅塞控制延遲
- 輕量標頭:僅 8 bytes,遠小於 TCP 的 20-60 bytes
- 支援廣播與多播(Multicast):可一對多傳送
- 訊息導向(Message-oriented):保留訊息邊界,一次
send對應一次recv
UDP 標頭結構
UDP 標頭結構極為簡潔,僅有 8 bytes:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 欄位 | 大小 | 說明 |
|---|---|---|
| Source Port | 16 bits | 來源端口號(可選,可設為 0) |
| Destination Port | 16 bits | 目的端口號 |
| Length | 16 bits | UDP 封包總長度(標頭 + 資料),最小 8 bytes |
| Checksum | 16 bits | 校驗和(IPv4 可選,IPv6 必須) |
對比 TCP 的 20+ bytes 標頭,UDP 的 8 bytes 標頭使其在傳輸小量資料時開銷更低、效率更高。
TCP vs UDP 完整比較
| 特性 | TCP | UDP |
|---|---|---|
| 連線方式 | 連接導向(三向交握) | 無連接 |
| 可靠性 | 保證到達、保證順序 | 不保證 |
| 傳輸方式 | 位元組串流(Byte Stream) | 訊息(Datagram) |
| 標頭大小 | 20-60 bytes | 8 bytes |
| 流量控制 | 有(滑動窗口) | 無 |
| 壅塞控制 | 有(Slow Start、AIMD 等) | 無 |
| 傳輸速度 | 較慢(有開銷) | 較快(低延遲) |
| 廣播 / 多播 | 不支援 | 支援 |
| 訊息邊界 | 不保留(串流) | 保留 |
| 適用場景 | 可靠性優先 | 速度 / 即時性優先 |
應用場景與常見協定對照
選擇 TCP 或 UDP 取決於應用對「可靠性」與「即時性」的需求。
使用 TCP 的場景
| 協定 | 端口 | 說明 |
|---|---|---|
| HTTP / HTTPS | 80 / 443 | 網頁傳輸,內容必須完整載入 |
| FTP | 20 / 21 | 檔案傳輸,檔案不可遺失 |
| SSH | 22 | 安全遠端登入,指令必須可靠傳送 |
| SMTP | 25 / 587 | 郵件發送 |
| MySQL | 3306 | 資料庫連線,SQL 查詢必須完整 |
使用 UDP 的場景
| 協定 | 端口 | 說明 |
|---|---|---|
| DNS | 53 | 域名解析查詢,單一小封包,快速回應 |
| DHCP | 67 / 68 | 動態 IP 分配 |
| VoIP(SIP) | 5060 | 語音通訊,低延遲比完整性重要 |
| NTP | 123 | 時間同步 |
| 線上遊戲 | 動態 | 遊戲狀態快速同步,舊資料不值得重傳 |
| QUIC(HTTP/3) | 443 | 在 UDP 上建構可靠傳輸,兼顧速度與可靠性 |
值得注意的是,DNS 是混合使用的代表:一般查詢使用 UDP(快速),但區域傳送(Zone Transfer)使用 TCP(確保完整性)。而 QUIC 協定則是在 UDP 之上實作了類似 TCP 的可靠性機制,代表了傳輸層技術的演進方向。
Socket 程式設計基礎
套接字(Socket)是應用程式存取傳輸層服務的 API 介面。一個 Socket 由 (IP 位址, 端口號, 協定) 唯一標識。以下以 Python 範例示範 TCP 與 UDP 的基本通訊。
TCP Socket 範例
# === TCP 伺服器端 (tcp_server.py) ===
import socket
# 建立 TCP Socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 綁定位址與端口
server.bind(('0.0.0.0', 8080))
# 開始監聽,最多排隊 5 個連線
server.listen(5)
print("TCP 伺服器啟動,等待連線...")
while True:
# 接受客戶端連線(阻塞等待)
client_socket, addr = server.accept()
print(f"收到來自 {addr} 的連線")
# 接收資料(最多 1024 bytes)
data = client_socket.recv(1024)
print(f"收到資料:{data.decode('utf-8')}")
# 回傳回應
client_socket.send("收到你的訊息!".encode('utf-8'))
# 關閉連線
client_socket.close()
# === TCP 客戶端 (tcp_client.py) ===
import socket
# 建立 TCP Socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 連線到伺服器(觸發三向交握)
client.connect(('127.0.0.1', 8080))
# 發送資料
client.send("Hello, TCP Server!".encode('utf-8'))
# 接收回應
response = client.recv(1024)
print(f"伺服器回應:{response.decode('utf-8')}")
# 關閉連線(觸發四次揮手)
client.close()
UDP Socket 範例
# === UDP 伺服器端 (udp_server.py) ===
import socket
# 建立 UDP Socket(注意使用 SOCK_DGRAM)
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 綁定位址與端口
server.bind(('0.0.0.0', 8081))
print("UDP 伺服器啟動,等待資料...")
while True:
# 接收資料與來源位址(無需建立連線)
data, addr = server.recvfrom(1024)
print(f"收到來自 {addr} 的資料:{data.decode('utf-8')}")
# 直接回傳給來源位址
server.sendto("收到你的 UDP 訊息!".encode('utf-8'), addr)
# === UDP 客戶端 (udp_client.py) ===
import socket
# 建立 UDP Socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 直接發送資料(不需 connect,不需握手)
client.sendto("Hello, UDP Server!".encode('utf-8'), ('127.0.0.1', 8081))
# 接收回應
response, server_addr = client.recvfrom(1024)
print(f"伺服器回應:{response.decode('utf-8')}")
client.close()
從程式碼可以清楚看出兩者的差異:TCP 需要 connect / accept 建立連線,使用 send / recv 傳輸;UDP 直接使用 sendto / recvfrom,不需要連線建立的步驟。
實用診斷指令
掌握網路診斷工具能幫助你快速排查 TCP/UDP 相關問題。
ss 指令(Socket Statistics)
ss 是取代 netstat 的現代工具,速度更快、資訊更完整。
# 顯示所有 TCP 連線
ss -t -a
# 顯示處於 LISTENING 狀態的 TCP socket 及其端口號
ss -tln
# 輸出範例:
# State Recv-Q Send-Q Local Address:Port Peer Address:Port
# LISTEN 0 128 0.0.0.0:80 0.0.0.0:*
# 顯示 ESTABLISHED 狀態的 TCP 連線與對應的程序
ss -tnp state established
# 輸出範例:
# ESTAB 0 0 192.168.1.10:22 192.168.1.5:54321 users:(("sshd",pid=1234,fd=3))
# 篩選特定端口
ss -tlnp 'sport = :80 or sport = :443'
# 顯示所有 UDP 連線
ss -u -a
# 統計各狀態的 TCP 連線數
ss -s
netstat 指令(傳統工具)
# 顯示所有 TCP 連線
netstat -ant
# 顯示所有 UDP 連線
netstat -anu
# 顯示 LISTENING 及其 PID
netstat -tlnp
# 統計各狀態的 TCP 連線數量
netstat -ant | awk '{print $6}' | sort | uniq -c | sort -rn
# 輸出範例:
# 15 ESTABLISHED
# 3 TIME_WAIT
# 2 LISTEN
# 1 CLOSE_WAIT
tcpdump 封包擷取
tcpdump 是命令列封包分析工具,可以即時擷取並顯示網路封包內容。
# 擷取特定端口的 TCP 封包
sudo tcpdump -i eth0 tcp port 80 -nn
# 擷取 TCP 三向交握封包(SYN 或 ACK 旗標)
sudo tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0' -nn
# 只擷取 SYN 封包(觀察新連線建立)
sudo tcpdump -i eth0 'tcp[tcpflags] == tcp-syn' -nn
# 擷取 UDP DNS 查詢
sudo tcpdump -i eth0 udp port 53 -nn
# 輸出範例:
# 10:30:15.123456 IP 192.168.1.10.54321 > 8.8.8.8.53: UDP, length 42
# 將封包存入檔案供 Wireshark 分析
sudo tcpdump -i eth0 -w capture.pcap -c 1000
其他實用指令
# 檢查特定端口是否開啟
nc -zv 192.168.1.10 80
# 輸出範例:
# Connection to 192.168.1.10 80 port [tcp/http] succeeded!
# 查看系統目前使用的 TCP 壅塞控制演算法
cat /proc/sys/net/ipv4/tcp_congestion_control
# 輸出範例:cubic
# 查看 ephemeral port 範圍
cat /proc/sys/net/ipv4/ip_local_port_range
# 輸出範例:32768 60999
安全考量
TCP 和 UDP 在設計時並未將安全性作為首要考量,因此它們各自面臨不同的攻擊威脅。
TCP 常見攻擊
| 攻擊類型 | 原理 | 防禦方式 |
|---|---|---|
| SYN Flood | 大量發送 SYN 封包但不完成三向交握,耗盡伺服器的半開連線佇列 | 啟用 SYN Cookies、限制 SYN 速率、防火牆過濾 |
| TCP Reset Attack | 偽造 RST 封包中斷合法連線 | 使用 TLS 加密、TCP MD5 簽章 |
| Session Hijacking | 猜測序號劫持已建立的連線 | 隨機化初始序號(ISN)、強制使用 TLS |
UDP 常見攻擊
| 攻擊類型 | 原理 | 防禦方式 |
|---|---|---|
| UDP Flood | 大量 UDP 封包淹沒目標,消耗頻寬與處理能力 | 速率限制、防火牆規則、DDoS 防護服務 |
| DNS Amplification | 偽造來源 IP 發送 DNS 查詢,利用回應放大攻擊流量 | 來源位址驗證(BCP38)、限制開放遞迴 DNS |
| NTP Amplification | 類似 DNS 放大,利用 NTP 的 monlist 指令產生大量回應 | 停用 monlist、限制存取來源 |
由於 UDP 是無連接協定,偽造來源 IP 位址相對容易,因此 UDP 常被用作放大攻擊(Amplification Attack)的載體。在實務上,建議對外暴露的 UDP 服務務必搭配速率限制與存取控制。
總結
TCP 與 UDP 作為傳輸層的兩大支柱,各有其設計哲學與適用場景。TCP 透過三向交握、序號確認、滑動窗口、壅塞控制等機制,提供了完整的可靠傳輸保障,適合對資料完整性要求嚴格的應用(如網頁瀏覽、檔案傳輸、資料庫操作)。UDP 則以極簡的 8 bytes 標頭和無連接設計,換取最低的傳輸延遲,適合對即時性要求高但能容忍少量資料遺失的場景(如 VoIP、線上遊戲、DNS 查詢)。
在實際開發中,理解這兩個協定的運作原理,不僅有助於選擇合適的傳輸層協定,也能在遇到網路問題時,利用 ss、tcpdump 等工具快速定位問題。而隨著 QUIC(HTTP/3)等新協定的出現,傳輸層技術正朝著「在 UDP 上建構可靠性」的方向演進,值得持續關注。
更多網路基礎知識,請參考 IP、Subnet Mask、Gateway 完整教學。