滑動窗口協(xié)議這樣理解更簡單
引言
想象一下這個場景:主機 A 一直向主機 B 發(fā)送數(shù)據(jù),不考慮主機 B 的接收能力,則可能導致主機 B 的接收緩沖區(qū)滿了而無法再接收數(shù)據(jù),從而導致大量的數(shù)據(jù)丟包,引發(fā)重傳機制。而在重傳的過程中,若主機 B 的接收緩沖區(qū)情況仍未好轉(zhuǎn),則會將大量的時間浪費在重傳數(shù)據(jù)上,降低傳送數(shù)據(jù)的效率。
所以引入了流量控制機制,主機 B 通過告訴主機 A 自己接收緩沖區(qū)的大小,來使主機 A 控制發(fā)送的數(shù)據(jù)量。總結來說:所謂流量控制就是控制發(fā)送方發(fā)送速率,保證接收方來得及接收。
TCP 實現(xiàn)流量控制主要就是通過 滑動窗口協(xié)議。
對于發(fā)送方來說,窗口大小就是指無需等待確認應答,可以連續(xù)發(fā)送數(shù)據(jù)的最大值。
窗口大小具體由誰來設定呢?
窗口大小和 TCP 報文首部中 16 位的 窗口大小 Window 字段有關:

該字段的含義是指自己接收緩沖區(qū)的剩余大小,于是發(fā)送端就可以根據(jù)這個接收端的處理能力來發(fā)送數(shù)據(jù),而不會導致接收端處理不過來。
所以,通常來說窗口大小是由接收方來決定的。
滑動窗口詳解
說了半天,窗口其實就是操作系統(tǒng)開辟的一個緩沖區(qū)
發(fā)送方在收到確認應答報文之前,必須在緩沖區(qū)中保留已發(fā)送的報文段(因為報文段可能在網(wǎng)絡中丟失,所以必須把這些未確認的報文段保留這,以便必要時重傳);如果在規(guī)定時間間隔內(nèi)收到接收方發(fā)來的確認應答報文,就可以將這些報文段從緩沖區(qū)中清除。
站在發(fā)送方的角度,滑動窗口可以分為四個部分:
已發(fā)送且已確認,這部分已經(jīng)發(fā)送完畢,可以忽略; 已發(fā)送但未確認,這部分可能在網(wǎng)絡中丟失,數(shù)據(jù)必須保留以便必要時重傳; 未發(fā)送但可發(fā)送,這部分接收方緩沖區(qū)還有空間保存,可以發(fā)出去; 未發(fā)送且暫不可發(fā)送,這部分已超出接收方緩沖區(qū)存儲空間,就算發(fā)出去也沒意義;
第 2 和第 3 部分加起來就剛好就是接收方窗口(緩沖區(qū))大小,它規(guī)定了當前發(fā)送方能發(fā)送的最大數(shù)據(jù)量。
當發(fā)送方收到接收方發(fā)來的確認應答后,就將緩沖區(qū)中那些被確認的報文清除出去,然后窗口向右移動,如下圖所示:

隨著雙方通信的進行,窗口將不斷向右移動,因此被形象地稱為滑動窗口(Sliding Window)
對于 TCP 的接收方,窗口稍微簡單點,分為三個部分:
已接收 未接收準備接收 (也即接收窗口,再強調(diào)一遍,接收窗口的大小決定發(fā)送窗口的大小,或者說,決定緩沖區(qū)的大小。) 未接收并未準備接收
由于 ACK 直接由 TCP 協(xié)議棧回復,默認無應用延遲,不存在 “已接收未回復 ACK”

綜上,舉個例子,假設發(fā)送方需要發(fā)送的數(shù)據(jù)總長度為 400 字節(jié),分成 4 個報文段,每個報文段長度是 100 字節(jié):
1)三次握手連接建立時接收方告訴發(fā)送方,我的接收窗口大小(rwnd) 是 300 字節(jié)
此時的接收方滑動窗口如下:

此時的發(fā)送方滑動窗口如下:

2)發(fā)送方發(fā)送第一個報文段(序號 1 - 100),還能再發(fā)送 200 個字節(jié)
3)發(fā)送方發(fā)送第二個報文段(序號 101 - 200),還能再發(fā)送 100 個字節(jié)
4)發(fā)送方發(fā)送第三個報文段(序號 201 - 300),還能再發(fā)送 0 個字節(jié)
此時,發(fā)送方的窗口(緩沖區(qū))中存了三個報文段了
此時的發(fā)送方滑動窗口如下:

5)接收方接收到了第一個報文段和第三個報文段,中間第二個報文段丟失。此時接收方返回一個報文段 ack = 101, rwnd = 200(假設這里發(fā)生流量控制,把窗口大小降到了 200,允許發(fā)送方繼續(xù)發(fā)送起始序號為 101,長度為 200 的報文)
此時的接收方滑動窗口如下(本來窗口右端應該右移,但是這里發(fā)生了流量控制,接收方希望縮小窗口大小,所以正好,這里就不需要向右擴展了):

發(fā)送方收到了第一個報文段的確認,從窗口(緩沖區(qū))中移除掉第一個報文段
此時的發(fā)送方滑動窗口如下:

6)發(fā)送方一直沒有收到第二個報文段的確認應答,在等待超時后重傳第二個報文段(序號 101 - 200)
7)接收方成功收到第二個報文段(緩沖區(qū)中有第二個和第三個報文段了),于是向發(fā)送方返回一個報文段 ack = 301, rwnd = 100(假設這里發(fā)生流量控制,把窗口大小降到了 100)
此時的接收方滑動窗口如下:(本來窗口右端應該右移,但是這里發(fā)生了流量控制,接收方希望縮小窗口大小,所以正好,這里就不需要向右擴展了)

發(fā)送方收到了第二個和第三個報文段的確認,從窗口(緩沖區(qū))中移除掉這倆報文段
8)發(fā)送方發(fā)送第四個報文段(序號 301 - 400)
此時的發(fā)送方滑動窗口如下:

通俗的例子
下面來更通俗地解釋下滑動窗口,看下面這個場景,老師說一段話,學生來記
最原始的模式,一股腦把所有的報文段全都發(fā)出去。
老師說 "危樓高百尺,手可摘星辰,不敢高聲語,恐驚天上人"(咱把每個字看成一個報文段,總共 20 個報文段)
學生寫道"危樓高百尺,手可......."
上面的模式過于簡單粗暴,發(fā)送方發(fā)送速度太快,接收方跟不上,并且重傳成本過高。
于是他們換了一種模式:每發(fā)送一個報文段就等待確認一個報文段,收到確認后才能發(fā)送下一個
老師說 "危",學生說"確認"
老師說 "樓",學生說"確認"
老師說 "高",學生說"高"
.........
上面的模式每發(fā)一個報文段,必須等到確認后才能再次發(fā)送,效率低下。
于是他們又換了一種模式:累積確認,既不是一股腦把所有的報文段全都發(fā)出去,也不是一次只發(fā)一個報文段,而是分組發(fā)送,每次發(fā)幾個報文段。
老師說 "危樓高百尺" (5 個報文段),學生說 "確認"
老師說 "手可摘星辰",學生說 "手可..."(3 個報文段丟失)
老師說 "不敢高聲語",學生說 "確認"
老師一直沒有收到 "摘星辰" 的確認,于是重新說了一遍 "摘星辰",學生說 "確認"
老師說 "恐驚天上人",學生說 "確認"
上面的模式提高了效率,連續(xù)多個報文段一起進行發(fā)送, 但是到底該怎么決定多少個報文段一起發(fā)送呢呢?
于是他們在上面模式的基礎上,做出了一些改進:滑動窗口,接收方認為狀態(tài)好(窗口比較大)的時候, 讓發(fā)送方每次多發(fā)一點;接收方認為狀態(tài)不好(窗口比較小)的時候,讓發(fā)送方每次少發(fā)送一點,起到一個流量控制的作用,限制發(fā)送方的速度。
學生告訴老師,我一次性可以接收 10 個報文段
老師說 "危樓高百尺,手可摘星辰",學生說 "危樓高百尺,手可..."(3 個報文段丟失,返回 ”可" 的確認應答,一共確認了 7 個報文段,老師的可用窗口右移,窗口中現(xiàn)在還有 “摘星辰” 3 個報文段)
學生說,我狀態(tài)不行,一次性現(xiàn)在只能接收 5 個報文段(流量控制,縮小窗口)
老師說 "不敢"(窗口中還有 “摘星辰” 3 個報文段,所以只能發(fā)送 2 個),學生說 "確認"
老師一直沒有收到 "摘星辰" 的確認,于是重新說了一遍,學生說 "確認"
(可用窗口恢復為 5 個)老師說 "恐驚天上人",......
心之所向,素履以往,我是小牛肉,小伙伴們下篇文章再見 ??
