為什麼 TCP 協議有粘包問題

脱氧核糖核苷酸 2024-09-21 11:44 4次浏览 0 条评论 taohigo.com

TCP/IP 協議簇建立瞭互聯網中通信協議的概念模型,該協議簇中的兩個主要協議就是 TCP 和 IP 協議。TCP/ IP 協議簇中的 TCP 協議能夠保證數據段(Segment)的可靠性和順序,有瞭可靠的傳輸層協議之後,應用層協議就可以直接使用 TCP 協議傳輸數據,不在需要關心數據段的丟失和重復問題。

圖 1 – TCP 協議與應用層協議

IP 協議解決瞭數據包(Packet)的路由和傳輸,上層的 TCP 協議不再關註路由和尋址2,那麼 TCP 協議解決的是傳輸的可靠性和順序問題,上層不需要關心數據能否傳輸到目標進程,隻要寫入 TCP 協議的緩沖區的數據,協議棧幾乎都能保證數據的送達。

當應用層協議使用 TCP 協議傳輸數據時,TCP 協議可能會將應用層發送的數據分成多個包依次發送,而數據的接收方收到的數據段可能有多個『應用層數據包』組成,所以當應用層從 TCP 緩沖區中讀取數據時發現粘連的數據包時,需要對收到的數據進行拆分。

粘包並不是 TCP 協議造成的,它的出現是因為應用層協議設計者對 TCP 協議的錯誤理解,忽略瞭 TCP 協議的定義並且缺乏設計應用層協議的經驗。本文將從 TCP 協議以及應用層協議出發,分析我們經常提到的 TCP 協議中的粘包是如何發生的:

  • TCP 協議是面向字節流的協議,它可能會組合或者拆分應用層協議的數據;
  • 應用層協議的沒有定義消息的邊界導致數據的接收方無法拼接數據;

很多人可能會認為粘包是一個比較低級的甚至不值得討論的問題,但是在作者看來這個問題還是很有趣的,不是所有人都系統性地學過基於 TCP 的應用層協議設計,也不是所有人對 TCP 協議都有那麼深入的理解,相信很多人學習編程的過程都是自底向上的,所以作者認為這是一個值得回答的問題,我們應該傳遞正確的知識,而不是負面的和居高臨下的情緒。

面向字節流

TCP 協議是面向連接的、可靠的、基於字節流的傳輸層通信協議3,應用層交給 TCP 協議的數據並不會以消息為單位向目的主機傳輸,這些數據在某些情況下會被組合成一個數據段發送給目標的主機。

Nagle 算法是一種通過減少數據包的方式提高 TCP 傳輸性能的算法4。因為網絡 帶寬有限,它不會將小的數據塊直接發送到目的主機,而是會在本地緩沖區中等待更多待發送的數據,這種批量發送數據的策略雖然會影響實時性和網絡延遲,但是能夠降低網絡擁堵的可能性並減少額外開銷。

在早期的互聯網中,Telnet 是被廣泛使用的應用程序,然而使用 Telnet 會產生大量隻有 1 字節負載的有效數據,每個數據包都會有 40 字節的額外開銷,帶寬的利用率隻有 ~2.44%,Nagle 算法就是在當時的這種場景下設計的。

當應用層協議通過 TCP 協議傳輸數據時,實際上待發送的數據先被寫入瞭 TCP 協議的緩沖區,如果用戶開啟瞭 Nagle 算法,那麼 TCP 協議可能不會立刻發送寫入的數據,它會等待緩沖區中數據超過最大數據段(MSS)或者上一個數據段被 ACK 時才會發送緩沖區中的數據。

圖 2 – Nagle 算法

幾十年前還會發生網絡擁塞的問題,但是今天的網絡帶寬資源不再像過去那麼緊張,在默認情況下,Linux 內核都會使用如下的方式默認關閉 Nagle 算法:

TCP_NODELAY = 1