TCP 協議有哪些缺陷?

小林coding發表於2022-05-09

作者:小林coding

圖解計算機基礎網站:https://xiaolincoding.com

大家好,我是小林。

忽然思考一個問題,TCP 通過序列號、確認應答、超時重傳、流量控制、擁塞控制等方式實現了可靠傳輸,看起來它很完美,事實真的是這樣嗎?TCP 就沒什麼缺陷嗎?

所以,今天就跟大家聊聊,TCP 協議有哪些缺陷?主要有四個方面:

  • 升級 TCP 的工作很困難;
  • TCP 建立連線的延遲;
  • TCP 存在隊頭阻塞問題;
  • 網路遷移需要重新建立 TCP 連線;

接下來,針對這四個方面詳細說一下。

升級 TCP 的工作很困難

TCP 協議是誕生在 1973 年,至今 TCP 協議依然還在實現更多的新特性。

但是 TCP 協議是在核心中實現的,應用程式只能使用不能修改,如果要想升級 TCP 協議,那麼只能升級核心。

而升級核心這個工作是很麻煩的事情,麻煩的事情不是說升級核心這個操作很麻煩,而是由於核心升級涉及到底層軟體和執行庫的更新,我們的服務程式就需要回歸測試是否相容新的核心版本,所以伺服器的核心升級也比較保守和緩慢。

很多 TCP 協議的新特性,都是需要客戶端和服務端同時支援才能生效的,比如 TCP Fast Open 這個特性,雖然在2013 年就被提出了,但是 Windows 很多系統版本依然不支援它,這是因為 PC 端的系統升級滯後很嚴重,W indows Xp 現在還有大量使用者在使用,儘管它已經存在快 20 年。

所以,即使 TCP 有比較好的特性更新,也很難快速推廣,使用者往往要幾年或者十年才能體驗到。

TCP 建立連線的延遲

基於 TCP 實現的應用協議,都是需要先建立三次握手才能進行資料傳輸,比如 HTTP 1.0/1.1、HTTP/2、HTTPS。

現在大多數網站都是使用 HTTPS 的,這意味著在 TCP 三次握手之後,還需要經過 TLS 四次握手後,才能進行 HTTP 資料的傳輸,這在一定程式上增加了資料傳輸的延遲。

TCP 三次握手和 TLS 握手延遲,如圖:

TCP 三次握手的延遲被 TCP Fast Open (快速開啟)這個特性解決了,這個特性可以在「第二次建立連線」時減少 TCP 連線建立的時延。

常規 HTTP 請求 與 Fast  Open HTTP 請求常規 HTTP 請求 與 Fast Open HTTP 請求

過程如下:

  • 在第一次建立連線的時候,服務端在第二次握手產生一個 Cookie (已加密)並通過 SYN、ACK 包一起發給客戶端,於是客戶端就會快取這個 Cookie,所以第一次發起 HTTP Get 請求的時候,還是需要 2 個 RTT 的時延;
  • 在下次請求的時候,客戶端在 SYN 包帶上 Cookie 發給服務端,就提前可以跳過三次握手的過程,因為 Cookie 中維護了一些資訊,服務端可以從 Cookie 獲取 TCP 相關的資訊,這時發起的 HTTP GET 請求就只需要 1 個 RTT 的時延;

TCP Fast Open 這個特性是不錯,但是它需要服務端和客戶端的作業系統同時支援才能體驗到,而 TCP Fast Open 是在 2013 年提出的,所以市面上依然有很多老式的作業系統不支援,而升級作業系統是很麻煩的事情,因此 TCP Fast Open 很難被普及開來。

還有一點,針對 HTTPS 來說,TLS 是在應用層實現的握手,而 TCP 是在核心實現的握手,這兩個握手過程是無法結合在一起的,總是得先完成 TCP 握手,才能進行 TLS 握手。

也正是 TCP 是在核心實現的,所以 TLS 是無法對 TCP 頭部加密的,這意味著 TCP 的序列號都是明文傳輸,所以就存安全的問題。

一個典型的例子就是攻擊者偽造一個的 RST 報文強制關閉一條 TCP 連線,而攻擊成功的關鍵則是 TCP 欄位裡的序列號位於接收方的滑動視窗內,該報文就是合法的。

為此 TCP 也不得不進行三次握手來同步各自的序列號,而且初始化序列號時是採用隨機的方式(不完全隨機,而是隨著時間流逝而線性增長,到了 2^32 盡頭再回滾)來提升攻擊者猜測序列號的難度,以增加安全性。

但是這種方式只能避免攻擊者預測出合法的 RST 報文,而無法避免攻擊者截獲客戶端的報文,然後中途偽造出合法 RST 報文的攻擊的方式。

大膽想一下,如果 TCP 的序列號也能被加密,或許真的不需要三次握手了,客戶端和服務端的初始序列號都從 0 開始,也就不用做同步序列號的工作了,但是要實現這個要改造整個協議棧,太過於麻煩,即使實現出來了,很多老的網路裝置未必能相容。

TCP 存在隊頭阻塞問題

TCP 是位元組流協議,TCP 層必須保證收到的位元組資料是完整且有序的,如果序列號較低的 TCP 段在網路傳輸中丟失了,即使序列號較高的 TCP 段已經被接收了,應用層也無法從核心中讀取到這部分資料。如下圖:

圖中傳送方傳送了很多個 packet,每個 packet 都有自己的序號,你可以認為是 TCP 的序列號,其中 packet #3 在網路中丟失了,即使 packet #4-6 被接收方收到後,由於核心中的 TCP 資料不是連續的,於是接收方的應用層就無法從核心中讀取到,只有等到 packet #3 重傳後,接收方的應用層才可以從核心中讀取到資料。

這就是 TCP 隊頭阻塞問題,但這也不能怪 TCP ,因為只有這樣做才能保證資料的有序性。

HTTP/2 多個請求是跑在一個 TCP 連線中的,那麼當 TCP 丟包時,整個 TCP 都要等待重傳,那麼就會阻塞該 TCP 連線中的所有請求,所以 HTTP/2 隊頭阻塞問題就是因為 TCP 協議導致的。

網路遷移需要重新建立 TCP 連線

基於 TCP 傳輸協議的 HTTP 協議,由於是通過四元組(源 IP、源埠、目的 IP、目的埠)確定一條 TCP 連線。

TCP 四元組TCP 四元組

那麼當移動裝置的網路從 4G 切換到 WIFI 時,意味著 IP 地址變化了,那麼就必須要斷開連線,然後重新建立 TCP 連線

而建立連線的過程包含 TCP 三次握手和 TLS 四次握手的時延,以及 TCP 慢啟動的減速過程,給使用者的感覺就是網路突然卡頓了一下,因此連線的遷移成本是很高的。

結尾

我記得之前在群裡看到,有位讀者位元組一面的時候被問到:「如何基於 UDP 協議實現可靠傳輸?

很多同學第一反應就會說把 TCP 可靠傳輸的特性(序列號、確認應答、超時重傳、流量控制、擁塞控制)在應用層實現一遍。

實現的思路確實這樣沒錯,但是有沒有想過,既然 TCP 天然支援可靠傳輸,為什麼還需要基於 UDP 實現可靠傳輸呢?這不是重複造輪子嗎?

所以,我們要先弄清楚 TCP 協議有哪些痛點?而這些痛點是否可以在基於 UDP 協議實現的可靠傳輸協議中得到改進?

現在市面上已經有基於 UDP 協議實現的可靠傳輸協議的成熟方案了,那就是 QUIC 協議,QUIC 協議把我本文說的 TCP 的缺點都給解決了,而且已經應用在了 HTTP/3。

所以,下次再聊聊 QUIC 協議是怎麼實現可靠傳輸的。

相關文章