傳輸層協定#

傳輸層負責端到端的資料傳輸,最重要的兩個協定是 TCP 和 UDP。它們代表了兩種截然不同的設計哲學。

TCP vs UDP 核心對比#

特性TCPUDP
連接面向連接(三次握手)無連接
可靠性保證送達、不丟失、不重複、按序不保證,盡力而為
資料形式字節流資料報
擁塞控制有,會主動降速無,應用層決定
狀態有狀態,維護複雜資料結構無狀態,簡單
頭部開銷20+ 字節8 字節

TCP 秉承「性惡論」,認為網路環境惡劣,必須通過複雜機制保證可靠;UDP 秉承「性善論」,相信網路基本能送達,把複雜性留給應用層。

UDP 協定#

UDP 包頭結構#

位元組0-12-3
0-3源埠號目標埠號
4-7長度校驗和
8+資料 …

UDP 頭部只有 8 個字節,極其簡單:

  • 源埠號、目標埠號:標識發送和接收的應用程式
  • 長度:UDP 資料報的總長度
  • 校驗和:資料完整性檢查

UDP 三大特點#

  1. 簡單輕量:沒有連接狀態維護,資源消耗小
  2. 不挑對象:可以一對一、一對多、多對多通信
  3. 不管擁塞:網路再差也不降速,一往無前

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 確保:
    1. 最後的 ACK 能到達對方(如果丢了,對方會重發 FIN)
    2. 本連接的所有舊包都已消亡,不會影響新連接

TCP 狀態機#

查看連接狀態:netstat -antp | grep ESTABLISHEDss -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:可靠、有序、面向連接,通過複雜機制保證資料完整送達
  • 三次握手:建立連接,確認雙方收發能力
  • 四次揮手:斷開連接,確保資料傳輸完畢
  • 流量控制:防止接收方被撐死
  • 擁塞控制:防止網路被塞死