TCP的滑動視窗和擁塞控制

看热闹的咸鱼發表於2024-07-19

當我們使用TCP,從客戶端傳送資料到伺服器,這個過程會是怎樣的呢?
首先,當然是耳熟能詳的三次握手過程,那當連線建立之後,就一股腦傳送所有資料嗎?
當然不是,一下子傳送太多資料,接收端可能沒有那麼大的空間,就浪費了流量。
TCP使用滑動視窗來管理傳送方和接收方之間的資料傳輸量。滑動視窗透過控制未確認資料包的數量,確保傳送方發出的包不會超出接收方的處理能力。

滑動視窗

滑動視窗的工作機制如下:

  • TCP在每個ACK包中,通知對方自己目前能接收多少資料,即TCP頭部中的視窗大小(三次握手期間的ACK也會包含視窗大小)。
  • 傳送方可以在這個視窗大小內,連續傳送多個資料包,而不必等待每個資料包的確認。
  • 當傳送方收到接收方的ACK確認,視窗就會向前滑動,允許傳送方繼續傳送新的資料包。

在某個時間段,傳送方的TCP資料流如下圖所示,可以分成4個部分:

  • 已傳送且已確認的資料:這部分資料已經沒用了,不再需要儲存。
  • 已傳送但未確認的資料:需要儲存在緩衝區裡,如果丟包了,可以進行重傳。
  • 未傳送,允許傳送的資料:沒有超過接收方緩衝區,可以傳送資料。
  • 未傳送,不允許傳送的資料:超過對方緩衝區,不可以傳送。

滑動視窗的優點:

  • 高效利用頻寬:滑動視窗允許傳送方連續傳送多個資料包,而無需等待每個資料包的確認,從而提高了頻寬利用率。
  • 流量控制:透過動態調整視窗大小,滑動視窗機制能有效控制資料流量,防止網路擁堵。

視窗滿的情況

在接收方看來,資料可以分成三個部分:

  • 成功接收並確認的資料
  • 未收到但可以接收的資料
  • 未收到且不可接收的資料

未收到但可以接收的部分,就是接收方的視窗大小。
接收方收到資料後,會存放到緩衝區中,等待上層應用獲取資料(socket呼叫read函式)。
如果上層應用繁忙,讀取效率較低,那麼這個視窗就會慢慢變小,甚至會變成0,也就是視窗滿的情況。
這時候,接收方會傳送一個ZeroWindow的包,告訴傳送方,這邊已經不能再接收資料了,傳送方就不再傳送資料。
等到緩衝區的資料被讀取之後,接收方會發一個WindowUpdate的ACK,告訴傳送方自己最新的視窗大小,傳送方就可以繼續傳送資料了。

但是這裡有個問題,如果這個WindowUpdate的包丟失了的話,傳送方就只能繼續保持0視窗,資料在這裡就卡住不再傳送了。
為了解決這個問題,TCP設定了定時探測,傳送ZeroWindowProbe,獲取接收端最新的視窗大小。

TCP協議本身並沒有一個明確的Window Full標記。然而,在實際使用中,有些網路監測工具和協議分析器(例如Wireshark)會標識或標記某些資料包,以表明傳送方的傳送視窗已經完全被使用。這種標記主要是用來幫助使用者理解和分析TCP連線中的流量控制和擁塞控制情況。

糊塗視窗綜合症

當接收方緩衝區滿時,視窗關閉,如果應用層讀取了一個位元組的資料,此時緩衝區就有了一個位元組的空間,這時候立刻傳送WindowUpdate通知傳送方的話,那傳送方就可能發一個位元組的資料過來,一個TCP包只包含一個位元組的資料,這效率就很低下。
這種情況下,這個連線的視窗一直保持在很小的狀態,稱作糊塗視窗綜合症
為了解決這個問題,當視窗大小小於min(MSS,快取空間/2) ,也就是小於MSS1/2快取大小中的最小值時,就會向傳送方通告視窗為 0,也就阻止了傳送方再發資料過來。
等到接收方處理了一些資料後,視窗大小 >= MSS,或者接收方快取空間有一半可以使用,才更新視窗大小,讓傳送方傳送資料過來。

擁塞控制

滑動視窗控制的是一個TCP連線的流量,避免傳送方的資料填滿接收方的快取。但是,網路上不只一個TCP連線,如果不加以控制的話,就可能發生資料的擁堵,擁堵導致丟包,丟包需要重傳,則又加大了擁堵。
所以,TCP使用了擁塞控制來避免資料填滿整個網路。

擁塞視窗 cwnd 是傳送方維護的一個的狀態變數,它會根據網路的擁塞程度動態變化的 。當cwnd=n時,表示傳送方可以傳送nMSS大小的資料

擁塞控制主要是四個演算法:

  • 慢啟動
  • 擁塞避免
  • 快速重傳
  • 快速恢復

慢啟動

慢啟動的思路就是,不要一開始就傳送大量的資料,先探測一下網路的擁塞程度,也就是說由小到大逐漸增加cwnd的大小,其演算法如下:

  1. 建立連線後,初始化cwnd1,可以傳送1個MSS資料。
  2. 每次收到ACK,則將cwnd加1。
  3. cwnd達到某一個閾值ssthresh(slow start threshold)後,不再使用慢啟動,改用擁塞避免演算法。

從上圖中,可以看到,每一個rtt時間,cwnd都會翻倍,從而快速地增長。在良好的網路環境下,可以很快達到閾值,進入擁塞避免演算法。

在一些現代作業系統中(如 Linux 和 Windows),TCP 初始擁塞視窗的預設值為 10 個 MSS。這使得傳送方在建立連線後的初始資料傳輸中,可以一次傳送多達 10 個 MSS 的資料包,而不必經歷傳統的慢啟動階段。

擁塞避免

慢啟動時,起點低,但指數增長,速度快,達到一定程度後,就不能再繼續指數增長,以防止擁塞。擁塞避免的想法就是,在一個rtt時間內,讓cwnd不是翻倍,而是加一,緩慢增長。
那麼,如何讓cwnd在一個rtt中加一呢?在慢啟動演算法中,在某一輪次,cwnd=n,此時連續傳送nMSS,每次收到ACKcwnd+1,收到n個則cwnd+n,形成翻倍的效果。同理,只要在每次收到ACK時,將cwnd+1改成cwnd+1/n,那麼在nACK後,則形成cwnd+1的結果。

在慢啟動和擁塞避免階段,如果出現超時,則重發超時的資料,然後處理如下:

  • ssthresh設為cwnd/2
  • cwnd設為1
  • 進入慢啟動演算法

快速重傳

當檢測是否丟包時,每次都要等待超時的發生,會浪費很長時間,因此引入了快速重傳:傳送方只要收到3個重複的ACK,即認為丟包發生,此時會立即重傳丟失的包,而不再等待超時的出現。

快速恢復

為了解決丟包後進入慢啟動引起的效率降低,在快速重傳的基礎上,又引入了快速恢復,在發生快速重傳之後,擁塞控制如下處理:

  • ssthresh設為cwnd/2
  • cwnd設為ssthresh+3(+3是因為已經收到3個重複的ACK)。
  • 如果再收到重複的ACK,則cwnd+1
  • 如果收到新的ACK,則快速恢復結束,進入擁塞避免

參考資料

  • 知乎 - 筆記:滑動視窗

相關文章