單元 06 Socket、UDP 與應用訊息邊界
學習目標與問題
理解單元 03 的 IP 與單元 05 的名稱解析後,本章建立應用與作業系統傳輸服務的介面。你應能寫出端點,解釋 bind、listen、accept、connect,並設計可靠的訊息切分。為何 send 兩次,對方卻不能保證 recv 兩次?
6.1 介面與端點
Socket 是程式使用網路服務的介面,不是某種實體線路。TCP 連線可用協定、來源 IP/埠、目的 IP/埠識別。伺服器的 listening socket 等待新連線,accept 取得每條連線自己的 socket;接收新客戶端不必為服務換一個固定埠。
bind 設定本地端點;connect 發起 TCP 連線或為 UDP 設定預設對端。UDP 的 connect 不建立 TCP 式可靠握手。127.0.0.1 僅本機 IPv4;::1 僅本機 IPv6;綁 0.0.0.0 可能接受其他介面流量。本課程程式固定 loopback,埠由系統挑選,避免佔用其他服務。
6.2 UDP 資料報
UDP 保留資料報邊界,通常每次 recvfrom 取得一份資料報及來源端點;buffer 太小時可能截斷或依平臺報錯,不能用第二次讀取補回同一份資料報。UDP 本身不提供可靠性、排序、流量控制或壅塞控制服務;應用若需要就要設計,例如 QUIC。在公共網路大量傳 UDP 也必須考慮壅塞與公平性。
資料報適合以可接受遺失與時效性為條件的工作。舊畫面晚到沒有價值時,重傳可能適得其反;但遊戲登入、狀態同步與檔案傳輸仍有不同可靠性需求。「UDP 都快、TCP 都慢」沒有指明工作負載。
6.3 TCP 位元組串流
TCP 提供一條有序位元組串流。send 可能只接受部分 bytes;Python sendall 幫忙持續傳送,仍不表示遠端應用已處理。recv 最多回指定長度,可能更少;回 b'' 表示對方傳送方向已正常結束。timeout 是等候期限耗盡,不等於收到 EOF。
應用需自己切訊息:固定長度、分隔符號、長度前綴或自描述格式。二進位可採「4-byte 大端長度 + payload」。讀取程式要一直累積到需要的長度,限制最大訊息,遇中途 EOF 就報錯。不得對每次 recv 直接做 JSON 解析,因為它可能含半個或多個 JSON。
例:先傳 00000003(十六進位)和 cat,再傳第二訊息。底層可切成 [前兩個長度 bytes]、[剩餘長度 + c]、[at + 下一訊息]。parser 的結果仍應是完整 cat,與如何切段無關。
6.4 HTTP 與計時
HTTP/1.1 有自己的標頭/內容界線;Content-Length 是 bytes 數,不是中文字數。使用現成 HTTP parser 可避免自行處理 chunked、關閉與持續連線的許多細節。L01 以標準函式庫觀察端點及 UTF-8 bytes。
記錄「開始 connect 到收到完整回應」與「既有連線送 request 到收到第一個 byte」是不同指標。OS 排程、應用處理和緩衝都會參與,不應宣稱一次 loopback 計時就是 TCP 傳播時間。
實驗、習題與檢核
做 L05:TCP 伺服器以每次最多兩個 bytes 讀取長度前綴與內容,正確解析多份訊息;另觀察 UDP 保留資料報邊界。兩者使用真實本機 socket,但無人為網路遺失。
- 為何不能假設 recv(1024) 一定收到 1024 bytes?
- 4-byte 長度說明內容 100 bytes,讀到 60 就 EOF,應如何處理?
- TCP 已 ACK 的 bytes 是否代表應用已寫入硬碟?
- 你會如何避免不可信長度讓 parser 設定過多記憶體?
檢核:能區分「應用訊息」「API 呼叫」「TCP 段」「IP 封包」。來源:Python socket 文件、RFC 768、RFC 9293。