TCP 實現可靠通信的兩種方式
我們都知道 IP 協議是“不太靠譜”。因為 IP 協議是不可靠的,所以 IP 數據包可能在傳輸過程中發生錯誤或者丟失。這就意味着,TCP 協議不得不面對以下三個問題。1)每個數據包有可能發送不成功 2)數據包在傳輸過程中有可能被丟棄 3)接收端有可能接受不到數據包
TCP 為了解決這丟包問題,提出兩個補救措施。
1.ACK 回復
在每收到一個正確的、符合次序的片段之后,就向發送方(也就是連接的另一段)發送一個特殊的 TCP 片段,用來知會(ACK,acknowledge)發送方:我已經收到那個片段了。這個特殊的 TCP片段 叫做 ACK 回復。如果一個片段序號為 L,對應ACK 回復有回復號 L+1,也就是接收方期待接收的下一個發送片段的序號。
2.重新發送機制
如果發送方在一定時間等待之后,還是沒有收到 ACK 回復,那么它推斷之前發送的片段一定發生了異常。發送方會重復發送那個出現異常的片段,等待 ACK 回復,如果還沒有收到,那么再重復發送原片段… 直到收到該片段對應的 ACK 回復(回復號為 L+1 的 ACK)。
TCP 的滑動窗口
雖然采用 “ACK 回復” + “重新發送機制” 方式能實現不丟包,但是會存在兩個問題。
1.效率低的問題。 stop-and-wait。stop-and-wait 雖然實現了 TCP 通信的可靠性,但同時犧牲了網絡通信的效率。同時,在等待ACK的時間段內,我們的網絡都處於閑置(idle)狀態
2.有點小缺陷
TCP 為了進一步優化解決這兩個問題,提出滑動窗口(sliding window)的概念。滑動窗口被同時應用於接收方和發送方, 發送方和接收方各有一個滑窗。當片段位於滑窗中時,表示 TCP 正在處理該片段。此外,如果滑窗中可以有多個片段,也就是可以同時處理多個片段。
我們借助一些圖片來進一步了解下滑動窗口內部機制。
黃色框框表示可以容納三個片段的固定大小的滑窗。在圖中,並假設片段從左向右排列。實際運用中,滑動窗口是可變的,窗口大小是字節(byte)來計算的。
對於發送方來說,滑窗的左側為已發送並已 ACK 過的片段序列,滑窗右側是尚未發送的片段序列。如果滑動窗口第一個片段一直沒有收到 ACK 回復,窗口不會向右滑動。但是發送方還是可以繼續發送后面兩個片段數據包。
對於接受方來說,滑窗的左側是已經正確收到並 ACK 回復過的片段,也就是正確接收到的文本流。滑窗中的片段是期望接收的片段。如果滑窗中第一個片段先收到, 滑窗會向右移動。如果滑窗中后面兩個片段先收到,但是第一個片段沒有收到。窗口不會向右滑動。
發送端已經發送三個數據包(1、2、3),在等待每個數據包的 ACK 回復
接收端成功收到兩個數據包,回復兩個 ACK。還有一個數據包沒有收到。當收到 數據包 1 時,接收端會回復一個 ACK 1,然后將窗口向有滑動一個位置。
發送端成功接收到 ACK 1 回復
發送端的窗口向右滑動一個位置
在沒有收到 ACK 2 和 3 的回復,還能繼續發送數據包 4
之前數據包 4 已經發送了。在之后成功收到 ACK 2 和 3 的回復,窗口向右滑動兩個位置,現在又能繼續發送數據包 5,6
通過上面一系列圖片,我們可以大致知道滑動窗口的機制。我們來做下小總結:
對於發送端
如果滑動窗口第一個片段一直沒有收到 ACK 回復,窗口不會向右滑動。但是發送方還是可以繼續發送后面兩個片段數據包。
對於接受端
如果滑窗中第一個片段先收到,滑窗會向右移動。如果滑窗中后面兩個片段先收到,但是第一個片段沒有收到。窗口不會向右滑動
那么實際應用中確實是這樣嗎?如果接收方每接受一個片段,就回復一個 ACK。這種效率有點低。所以實際應用中, TCP 協議為了減少了 ACK 回復所消耗的流量,采用的是累計 ACK 回復。 接收方往往利用一個 ACK 回復來知會連續多個片段的成功接收。通過累計 ACK,所需要的 ACK 回復通常可以降到 50%。
我們同樣通過圖片的形式來了解累計 ACK 回復的原理。
在圖中,橙色為已經接收的片段。方框為滑窗,滑窗可容納3個片段。
情況1:滑窗還沒接收到片段 7 時,已接收到片段 8,9。這樣就在滑窗中制造了一個“空穴”(hole)。
情況2:當滑窗最終接收到片段7時,滑窗送出一個回復號為 10 的 ACK 回復。發送方收到該回復,會意識到,片段 10 之前的片段已經按照次序被成功接收。整個過程中節約了片段 7 和片段 8 所需的兩個 ACK 回復。