傳輸層協定#
傳輸層負責端到端的資料傳輸,最重要的兩個協定是 TCP 和 UDP。它們代表了兩種截然不同的設計哲學。
TCP vs UDP 核心對比#
| 特性 | TCP | UDP |
|---|---|---|
| 連接 | 面向連接(三次握手) | 無連接 |
| 可靠性 | 保證送達、不丟失、不重複、按序 | 不保證,盡力而為 |
| 資料形式 | 字節流 | 資料報 |
| 擁塞控制 | 有,會主動降速 | 無,應用層決定 |
| 狀態 | 有狀態,維護複雜資料結構 | 無狀態,簡單 |
| 頭部開銷 | 20+ 字節 | 8 字節 |
TCP 秉承「性惡論」,認為網路環境惡劣,必須通過複雜機制保證可靠;UDP 秉承「性善論」,相信網路基本能送達,把複雜性留給應用層。
UDP 協定#
UDP 包頭結構#
| 位元組 | 0-1 | 2-3 |
|---|---|---|
| 0-3 | 源埠號 | 目標埠號 |
| 4-7 | 長度 | 校驗和 |
| 8+ | 資料 … |
UDP 頭部只有 8 個字節,極其簡單:
- 源埠號、目標埠號:標識發送和接收的應用程式
- 長度:UDP 資料報的總長度
- 校驗和:資料完整性檢查
UDP 三大特點#
- 簡單輕量:沒有連接狀態維護,資源消耗小
- 不挑對象:可以一對一、一對多、多對多通信
- 不管擁塞:網路再差也不降速,一往無前
UDP 適用場景#
| 場景 | 原因 | 範例 |
|---|---|---|
| 內網通信 | 環境好,丟包少 | DHCP, TFTP |
| 廣播/多播 | 不需要一對一連接 | DHCP, VXLAN |
| 實時應用 | 低延遲優先,可容忍丟包 | 直播、遊戲、VoIP |
| 自定義可靠性 | 應用層實作更適合的策略 | QUIC, 遊戲協定 |
基於 UDP 的「城會玩」應用
QUIC 協定:Google 提出的基於 UDP 的傳輸協定,HTTP/3 的底層協定。在應用層實作快速連接建立、自適應擁塞控制,比 TCP 更低延遲。
直播流媒體:影片直播寧可丟幀也不要卡頓。TCP 的嚴格順序會導致一個包丟失,後面所有包都要等待,用戶體驗極差。
實時遊戲:玩家關心的是當前狀態,過期資料毫無意義。遊戲會基於 UDP 自定義可靠性策略,只重傳關鍵資料。
物聯網:設備資源有限,無法承擔 TCP 的複雜狀態維護。Thread 等物聯網協定基於 UDP。
TCP 協定#
TCP 要解決五個核心問題:
| 核心問題 | 解決機制 |
|---|---|
| 順序問題 | 序列號機制 |
| 丢包問題 | 確認回應 + 超時重傳 |
| 連接維護 | 三次握手 + 四次揮手 |
| 流量控制 | 滑動窗口 |
| 擁塞控制 | 慢啟動、擁塞避免、快速恢復 |
TCP 包頭結構#
TCP 頭部至少 20 字節,比 UDP 複雜得多:
- 源埠號、目標埠號:各 16 位
- 序列號 (Sequence Number):解決亂序問題
- 確認號 (Acknowledgment Number):解決丢包問題
- 標誌位:SYN、ACK、FIN、RST 等,控制連接狀態
- 窗口大小:流量控制
三次握手(建立連接)#
sequenceDiagram
participant C as 客戶端
participant S as 服務端
C->>S: SYN, seq=x<br/>(你好,我是 A)
S->>C: SYN+ACK, seq=y, ack=x+1<br/>(你好 A,我是 B,收到了)
C->>S: ACK, ack=y+1<br/>(好的 B)
Note over C,S: 連接建立完成為什麼是三次,不是兩次?
兩次握手無法確認雙方的收發能力都正常。三次握手確保:客戶端確認我能發、我能收;伺服器端確認我能發、我能收。如果只有兩次,伺服器端無法確認自己發出的包客戶端能收到。
為什麼不是四次?
理論上可以四次甚至更多次,但三次已經足夠建立可靠連接。更多次握手只是增加延遲,不能提高可靠性。
序列號為什麼隨機?
每個連接的初始序列號都不同(基於時間的 32 位計數器),防止舊連接的殘留包被新連接誤收。
四次揮手(斷開連接)#
sequenceDiagram
participant C as 客戶端
participant S as 伺服器端
C->>S: FIN (我不玩了)
S->>C: ACK (好的,知道了)
Note over S: B 可能還有資料要發送
S->>C: FIN (我也不玩了)
C->>S: ACK (好的,拜拜)
Note over C: 等待 2MSL為什麼是四次,不是三次?
TCP 是全雙工的,每個方向的連接需要單獨關閉。A 說不發了,但 B 可能還有資料要發,所以 B 的 ACK 和 FIN 是分開的。
為什麼要等待 2MSL?
- MSL (Maximum Segment Lifetime):報文最大生存時間,通常 30 秒到 2 分鐘
- 等待 2MSL 確保:
- 最後的 ACK 能到達對方(如果丢了,對方會重發 FIN)
- 本連接的所有舊包都已消亡,不會影響新連接
TCP 狀態機#
查看連接狀態:
netstat -antp | grep ESTABLISHED或ss -antp;查看 TIME_WAIT 數量:netstat -an | grep TIME_WAIT | wc -l
常見狀態:
| 狀態 | 說明 |
|---|---|
| LISTEN | 伺服器端監聽中 |
| SYN_SENT | 客戶端已發 SYN,等待回應 |
| SYN_RCVD | 伺服器端收到 SYN,已回 SYN+ACK |
| ESTABLISHED | 連接已建立,可以傳輸資料 |
| FIN_WAIT_1/2 | 主動關閉方等待對方回應 |
| CLOSE_WAIT | 被動關閉方等待應用層關閉 |
| TIME_WAIT | 主動關閉方等待 2MSL |
| CLOSED | 連接已關閉 |
流量控制與擁塞控制#
流量控制(接收方視角)
通過滑動窗口機制,接收方告訴發送方自己的處理能力,防止被撐死。
擁塞控制(網路視角)
flowchart LR
subgraph CC["擁塞控制階段"]
A["慢啟動<br/>(指數增長)"] --> B["達到閾值"]
B --> C["擁塞避免<br/>(線性增長)"]
end
style A fill:#c8e6c9
style C fill:#fff3e0- 慢啟動:從小窗口開始,指數級增長
- 擁塞避免:達到閾值後,線性增長
- 快速重傳:收到 3 個重複 ACK,立即重傳
- 快速恢復:不從頭開始慢啟動
Socket 編程#
Socket 是應用程式與 TCP/UDP 協定交互的介面。
基本流程#
TCP Server:
socket() → bind() → listen() → accept() → recv()/send() → close()TCP Client:
socket() → connect() → send()/recv() → close()UDP:
socket() → bind() → recvfrom()/sendto() → close()Python Socket 範例
TCP Server:
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8080))
server.listen(5)
while True:
client, addr = server.accept()
data = client.recv(1024)
client.send(b'Hello from server')
client.close()TCP Client:
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
client.send(b'Hello from client')
data = client.recv(1024)
client.close()本章小結#
- UDP:簡單、快速、無連接,適合實時性要求高、可容忍丢包的場景
- TCP:可靠、有序、面向連接,通過複雜機制保證資料完整送達
- 三次握手:建立連接,確認雙方收發能力
- 四次揮手:斷開連接,確保資料傳輸完畢
- 流量控制:防止接收方被撐死
- 擁塞控制:防止網路被塞死