為什麼不應該把TCP思維套在UDP上

  • 為什麼不應該把TCP思維套在UDP上

為什麼不應該把 TCP 思維套在 UDP 上?

結構差異

TCP表頭
UDP表頭

TCP 上的概念很多: 建立通路, 資源使用, 資料傳輸, 可靠傳輸, 基於重複累計確認的重傳, 超時重傳, 校驗和, 流量控制, 擁塞控制, 最大分段大小, 選擇確認, TCP 視窗縮放選項, TCP 時間戳, 強制資料交付, 終結通路.

以上這些能力, UDP 基本上都沒有, 它僅比鏈路層多一點區分應用層目的的能力. UDP 足夠簡單意味著足夠靈活.

如果可能發生,則一定會發生

墨菲定律:

如果有多過一種方式去做某事,而其中一種方式將導致災難,則必定有人會這樣選擇。

通常介紹 UDP 適合應用在遊戲/語音/視訊等場景, 少量的錯包不影響業務. 為什麼 UDP 適合這些場景? 它能用在這些場景, 不代表它是這些場景的最優方案, 必然是存在 TCP 無法解決的問題, 才讓這些服務選擇了功能簡陋的 UDP 協定. 錯包不影響業務擴展開來講是指 TCP 協定在乎錯包, UDP 不在乎錯包, 更在乎即時性/連續性. UDP 的特點就是它不在乎 TCP 在乎的因素, 這些因素影響了即時性.

在程式碼實現上, UDP 只需要建立一個 socket, 繫結到一個埠上, 即可以開始收發. 通常 socket 用完時, 埠也用完了.

因此我可以這樣使用 UDP:

  1. 往任意 IP 的任意埠發送隨機封包, 看看哪個埠有響應
  2. 甲通過 A 埠, 將請求封包發送到乙的 B 埠; 乙將響應封包用 C 埠, 發給甲的 D 埠
  3. 甲通過 A 埠, 將請求封包發送到乙的 B 埠; 乙委託丙將響應封包用 C 埠, 發給甲的 D 埠
  4. 甲通過 A 埠, 將請求封包發送到乙的 B 埠, 但將發送封包的來源 IP 修改為了丙的 IP, 乙將會將響應封包發往丙
  5. 雙方協商各用 10 個 UDP 埠, 同時進行接受和發送

這些方法在 TCP 裡自然行不通, 但在 UDP 協定中, 只要可以這樣做, 就一定會有人這樣做. 所以當把 TCP 的一些思維套在 UDP 上是一種理想主義, 真實情況常常不是我們能枚舉完的.

UDP 的封包非常簡單, 使用也非常靈活, 原本沒有連接的概念, 需要自己定義 UDP 連接. 嘗試了一些定義方法, 都不能完全準確達到連接方向判斷意圖, 這時需要接納一些容錯, 毕竟原本就沒有 UDP 連接的定義, 當各方對 UDP 連接的定義不一致時, 必然會導致行為與預期不一樣.

客戶端視角的 UDP

語音/視訊等業務常會產生丟包, 但是丟包方式的不同對業務有著不同的影響. 比如說 30%的丟包是均勻發生的, 還是全丟在某個時間段, 對體驗的影響有明顯的區分. 顯然, 我們期待的是更均勻的丟包. 可是 UDP 沒有流量控制防止方法, 如何丟包則有一些方法. 儘管 UDP 通訊常被描述為"盡力而為", 但是不同方式的"盡力"會達到不同的效果.

服務商視角的 UDP

如果是 TCP 攻擊, 客戶端需要一定的開銷, 建立連接, 維護連接, 也就是攻擊者需要付出一定的代價. 而在 UDP 攻擊中, 攻擊者付出的代價小很多, 如果攻擊者想消耗的就是服務方的頻寬流量, UDP 是一個很好的方式. 比如說服務購買了 100GB 的不限速流量, 處理能力僅 10MB 每秒, 但接受速度 1GB 每秒, 那麼 90%的請求流量無效, 但這些流量不是免費的. 服務方應該避免產生這種情況.

運營商的視角的 UDP

完成一次通訊需包含多個終端以及通訊通道, 受關注的總是服務端和客戶端, 其實運營商的視角同樣重要. DDoS 攻擊中, 我們常關心服務端的資源消耗情況, 實際上運營商的資源也是有限的, 服務端簡單不回應請求, 但接收流量卻已經消耗了頻寬, 只是這個資源一般屬於運營商. 我們在壓力測試中常用到"丟包率"指標, 這個指標表達的完整通訊鏈條中的丟包, 而不僅僅是服務端的丟包. 運營商也會丟包. 在運營商看, 服務方僅購買了 1MB/s 的頻寬, 但客戶端以 1GB/s 的速度發送, 雙方都不必為浪費的流量付費, 是運營商承擔了這部分頻寬的代價. 因此, 運營商必然想辦法屏蔽這種流量, 也就是 UDP 的 QoS. 在 TCP 中有擁塞控制, 但在 UDP 中, 運營商可以透過丟包來控制流量. 實際情況中, 運營商更加簡單粗暴, 直接屏蔽長時間使用的埠的流量, 也就是 UDP 的埠屏蔽. 在微信通話的實際測試中發現, 每一通電話客戶端會使用多個埠, 其中有一個 UDP 埠會和同一伺服器的 6 個 UDP 埠進行通訊, 推測就是為了應對運營商的埠屏蔽.

總結

UDP 的靈活表示在實現一個目標時, 它有著多種實現方式, 並且都是合法的, 只要能最終實現穩定的通訊, 不管它實現的如何和 TCP 大相徑庭, 都是"存在即合理"的. 因而, 我們不能完全將 TCP 的概念套用在 UDP 上, 即便為了產品設計, 創造了新的 UDP 連接定義, 也應該能預期並允許出錯, 毕竟"允許出錯"就是 UDP 的核心功能, 這是 UDP 的優勢, 不是它的缺點, 是服務主動選擇的協定核心能力, 而不是不得不接受的缺點.

更多閱讀