TCP可靠機制詳解

桂洛克船长發表於2024-11-24

重傳機制

針對資料包丟失的情況,會用重傳機制解決。

超時重傳

在傳送資料時,設定一個定時器,當超過指定的時間後,沒有收到對方的ACK 確認應答報文,就會重發該資料,也就是我們常說的超時重傳。TCP 會在以下兩種情況發生超時重傳:資料包丟失,確認應答丟失

缺點:

  • 當超時時間 RTO 較大時,重發就慢,丟了老半天才重發,沒有效率,效能差;

  • 當超時時間 RTO 較小時,會導致可能並沒有丟就重發,於是重發的就快,會增加網路擁塞,導致更多的超時,更多的超時導致更多的重發。

所以超時重傳時間 RTO 的值應該略大於報文往返 RTT 的值。

快速重傳

TCP 還有另外一種快速重傳(Fast Retransmit)機制,它不以時間為驅動,而是以資料驅動重傳

假設傳送方發出了 1,2,3,4,5 份資料,但是第二個資料丟失,1,3,4,5到了接收端只會回覆ack=2,當3,4,5都到達後,傳送者會收到3個ack=2的確認,知道了 Seq2 還沒有收到,就會在定時器過期之前,重傳丟失的 Seq2。

但是它依然面臨著另外一個問題。就是重傳的時候,是重傳一個,還是重傳所有的問題。

舉個例子,假設傳送方發了 6 個資料,編號的順序是 Seq1 ~ Seq6 ,但是 Seq2、Seq3 都丟失了,那麼接收方在收到 Seq4、Seq5、Seq6 時,都是回覆 ACK2 給傳送方,但是傳送方並不清楚這連續的 ACK2 是接收方收到哪個報文而回復的, 那是選擇重傳 Seq2 一個報文,還是重傳 Seq2 之後已傳送的所有報文呢。

  • 如果只選擇重傳 Seq2 一個報文,那麼重傳的效率很低。因為對於丟失的 Seq3 報文,還得在後續收到三個重複的 ACK3 才能觸發重傳。
  • 如果選擇重傳 Seq2 之後已傳送的所有報文,雖然能同時重傳已丟失的 Seq2 和 Seq3 報文,但是 Seq4、Seq5、Seq6 的報文是已經被接收過了,對於重傳 Seq4 ~Seq6 折部分資料相當於做了一次無用功,浪費資源。

SACK選擇性確認

這種方式需要在 TCP 頭部「選項」欄位里加一個 SACK 的東西,它可以將已收到的資料的資訊傳送給「傳送方」,這樣傳送方就可以知道哪些資料收到了,哪些資料沒收到,知道了這些資訊,就可以只重傳丟失的資料

滑動視窗

為什麼引入視窗

TCP 是每傳送一個資料,都要進行一次確認應答。當上一個資料包收到了應答了, 再傳送下一個。但這種方式的缺點是效率比較低的。為解決這個問題,TCP 引入了視窗這個概念。

原理

假設視窗大小為 3 個 TCP 段,那麼傳送方就可以「連續傳送」 3 個 TCP 段,並且中途若有 ACK 丟失,可以透過「下一個確認應答進行確認」。意味著 所有資料「接收方」都收到了。這個模式就叫累計確認或者累計應答

視窗大小誰決定

TCP 頭裡有一個欄位叫 Window,也就是視窗大小。這個欄位是接收端告訴傳送端自己還有多少緩衝區可以接收資料。所以,通常視窗的大小是由接收方的視窗大小來決定的。

傳送方:

image-20241116103031193

接收方:

image-20241116103057258

流量控制

作業系統與滑動視窗的關係

  • 當應用程式沒有及時讀取快取時:假設客戶端與服務端在進行正常資料發收,但是服務端非常繁忙,每次應用程序只讀取了部分位元組,於是就有剩餘的位元組佔著緩衝區,於是視窗就會收縮,當進行多次這種情況後,視窗最後會變為0,也就是發生了視窗關閉。

  • 當服務端系統資源緊張時,作業系統可能會直接減少了接收緩衝區大小:服務端因繁忙,作業系統就把接收快取減少位元組。這時應用程式又無法及時讀取快取資料,那麼這時候就有嚴重的事情發生了,會出現資料包丟失的現象。

為了防止這種情況發生,TCP 規定是不允許同時減少快取又收縮視窗的,而是採用先收縮視窗,過段時間再減少快取,這樣就可以避免了丟包情況。

視窗關閉

如果視窗大小為 0 時,就會阻止傳送方給接收方傳遞資料,直到視窗變為非 0 為止,這就是視窗關閉。

當發生視窗關閉時,接收方處理完資料後,會向傳送方通告一個視窗非 0 的 ACK 報文,如果這個通告視窗的 ACK 報文在網路中丟失了,那麻煩就大了。這會導致傳送方一直等待接收方的非 0 視窗通知,接收方也一直等待傳送方的資料,造成了死鎖的現象。

為了解決這個問題,TCP 為每個連線設有一個持續定時器,只要 TCP 連線一方收到對方的零視窗通知,就啟動持續計時器。

如果持續計時器超時,就會傳送視窗探測 ( Window probe ) 報文,而對方在確認這個探測報文時,給出自己現在的接收視窗大小。

擁塞控制

在網路出現擁堵時,如果繼續傳送大量資料包,可能會導致資料包時延、丟失等,這時 TCP 就會重傳資料,但是一重傳就會導致網路的負擔更重,於是會導致更大的延遲以及更多的丟包

於是,就有了擁塞控制,控制的目的就是避免「傳送方」的資料填滿整個網路。又引入了擁塞視窗的概念

擁塞視窗 cwnd是傳送方維護的一個的狀態變數,它會根據網路的擁塞程度動態變化的

  • 只要網路中沒有出現擁塞,cwnd 就會增大;
  • 但網路中出現了擁塞,cwnd 就減少;

那麼怎麼知道當前網路是否出現了擁塞呢?

其實只要「傳送方」沒有在規定時間內接收到 ACK 應答報文,也就是發生了超時重傳,就會認為網路出現了擁塞。

慢啟動

慢啟動的演算法記住一個規則就行,當傳送方每收到一個 ACK,擁塞視窗 cwnd 的大小就會加 1。

增加到什麼時候:

慢啟動門限 ssthresh (slow start threshold)狀態變數。

  • cwnd < ssthresh 時,使用慢啟動演算法。
  • cwnd >= ssthresh 時,就會使用「擁塞避免演算法」

擁塞避免

進入擁塞避免演算法後,它的規則是:每當收到一個 ACK 時,cwnd 增加 1/cwnd。

也就是說擁塞視窗的增長速度從指數增長變成線性增長,就這麼一直增長著後,網路就會慢慢進入了擁塞的狀況了,於是就會出現丟包現象,這時就需要對丟失的資料包進行重傳。

當觸發了重傳機制,也就進入了「擁塞發生演算法」。

擁塞發生演算法

發生超時重傳的擁塞發生演算法:

這個時候,ssthresh 和 cwnd 的值會發生變化:

  • ssthresh 設為 cwnd/2
  • cwnd 重置為 1 (是恢復為 cwnd 初始化值,我這裡假定 cwnd 初始化值 1)

接著,就重新開始慢啟動,這種方式太激進了,反應也很強烈,會造成網路卡頓。

發生快速重傳的擁塞發生演算法:

  • cwnd = cwnd/2 ,也就是設定為原來的一半;
  • ssthresh = cwnd;
  • 進入快速恢復演算法

快速恢復

快速重傳和快速恢復演算法一般同時使用

進入快速恢復演算法如下:

  • 擁塞視窗 cwnd = ssthresh + 3 ( 3 的意思是確認有 3 個資料包被收到了);
  • 重傳丟失的資料包;
  • 如果再收到重複的 ACK,那麼 cwnd 增加 1;
  • 如果收到新資料的 ACK 後,把 cwnd 設定為第一步中的 ssthresh 的值,如果收到新資料的 ACK 後,把 cwnd 設定為第一步中的 ssthresh 的值,可以回到恢復之前的狀態了,也即再次進入擁塞避免狀態;

相關文章