滑動窗口協議(Sliding Window Protocol),屬於
TCP協議的一種應用,用於網絡數據傳輸時的流量控制,以免擁塞的發生。該協議容許發送方在中止並等待確認前發送多個數據分組。因爲發送方沒必要每發一個分組就停下來等待確認,所以該協議能夠加速數據的傳輸,提升網絡吞吐量。
-
中文名
-
滑動窗口協議
-
外文名
-
Sliding Window Protocol
-
類 型
-
可靠數據傳輸協議
-
層次結構
-
4層
若是過多的源同時以很快的速度發送大量的數據包,而此時接收方並無如此高的接收數據的能力,所以極易致使網絡的擁塞。因此,爲了控制發送方的發送速度,防止發送方並考慮到受發送緩衝區大小的制約等,要求對發送方已發出但還沒有經確認的幀的數目加以限制,同時使網絡的傳輸效率獲得提升,滑動窗口協議應運而生,它使得發送方能夠在未收到確認的狀況下,同時發送多個數據分組,由此大大提高了網絡吞吐量。
在任何基於自動重發請求進行錯誤控制的通訊協議中,接收方必須確認收到的數據包。 若是發送方在合理的時間內沒有收到確認,則重發數據。沒有聽到確認的發送方不知道接收方是否實際接收到分組(數據可能在傳輸中丟失或損壞)。 若是錯誤檢測顯示損壞,則數據包將被接收方忽略,而且不會發送確認。 由於網絡傳輸的時延,將有大量時間被用於等待確認,致使傳輸效率低下。
傳輸的每一個部分被分配惟一的連續序列號,接收方使用數字並以正確的順序放置接收到的數據包,丟棄重複的數據包並識別丟失的數據。
協議中規定,對於窗口內未經確認的分組須要重傳。這種分組的數量最多能夠等於發送窗口的大小,即滑動窗口的大小n減去1(由於發送窗口不可能大於(n-1),起碼接收窗口要大於等於1)。
滑動窗口協議必須保證數據包的按序傳輸,發送窗口中的序列號表明已發送但還沒有收到確認的數據包,發送窗口可持續地維持一系列未經確認的數據包,由於發送方窗口內的數據包可能在傳輸過程當中丟失或損壞,因此發送過程必須把發送窗口中的全部數據包保存起來以備重傳。發送窗口一旦達到最大值,發送過程就必須中止接收新的數據包,直到有空閒緩存區。接收窗口外的數據包都要丟棄,當序列號等於接收窗口下限的數據包到達時,把它提交給應用程序並向發送端發送確認,接收窗口向前移動一位。發送窗口和接收窗口上下限無需相同,大小也無需相同,但接收窗口大小需保持固定,發送窗口大小可隨着數據包而改變。
[1]
滑動窗口協議的時間線
工做原理
經過限制在任何給定時間能夠發送或接收的數據包的數量,滑動窗口協議容許使用固定大小的序列號傳送無限數量的數據包。發送方側的術語「窗口」表示接收方還沒有確認的分組總數的邏輯邊界。接收方在每一個確認包中通知發送器當前的最大接收緩衝區大小(窗口邊界)。 TCP報頭使用16位字段向發送方報告接收窗口大小。所以,可使用的最大窗口是2^16 = 64千字節。在慢啓動模式下,發送器以低分組計數器開始,而且在從接收方接收到確認分組以後增長每一個傳輸中的分組數量。對於接收的每一個ACK分組,該窗口經過一個分組(邏輯地)滑動以傳送一個新分組。當達到窗口閾值時,發送器發送一個包,用於接收到的一個ACK分組(確認分組)。若是窗口限制爲10個數據包,則在慢啓動模式下,發送器能夠開始發送一個數據包,後跟兩個數據包(發送兩個數據包以前必須接收一個數據包),其次是三個數據包等等,直到10個數據包。可是在達到10個分組以後,進一步的傳輸被限制爲一個接收到的一個分組發送的分組。在仿真中,看起來好像窗口對於接收到的每一個ACK分組移動一個分組距離。在接收方側,窗口也會爲接收到的每一個數據包移動一個數據包。滑動窗口方法能夠確保網絡上的交通擁堵得以免。應用層仍將提供傳輸到TCP的數據,而不用擔憂網絡流量擁塞問題,由於發送方和接收方的TCP實現分組緩衝區的滑動窗口。窗口大小可能根據網絡流量而動態變化。
操做
發送方和接收方分別具備當前序列號nt和nr。它們各自還有一個窗口大小wt和wr。窗口大小可能會根據網絡流量的變化而有所不一樣,可是在更簡單的實現中它們是固定的。窗口大小必須大於零才能進行任何操做。
一般狀況下,nt是要發送到下一個分組,即還沒有發送的第一分組的序列號。一樣地,nr還沒有收到的第一個分組的序列號。這兩個序列號會隨着時間逐漸增長。
接收方還能夠跟蹤未接收到的最高序列號,變量ns比接收到的最高序列號還多一。對於僅接受數據包(wr = 1)的簡單接收方,這與nr相同,但若是wr> 1,則能夠更大。注意區別:已經收到nr如下的全部數據包,沒有接收到ns以上的任何數據包,在nr和ns之間,已經收到一些數據包。
當接收方接收到一個數據包時,它會適當地更新其變量,並用新的nr發送確認。發送方跟蹤其收到的最高確認。發送方知道已經接收到但不包括na的全部分組,可是對於na和ns之間的分組是不肯定的,即na≤nr≤ns。
而且序列號老是符合na≤nr≤nx≤nt≤na+wt的規則,證實以下:
na≤nr:發送器接收到的最高確認不能高於接收方確認的最高nr。
nr≤ns:徹底接收的數據包的範圍不能超出部分接收的數據包的結尾。
ns≤nt:接收到的最高報文不能高於發送的最高報文。
nt≤na + wt:發送的最高數據包同時受到接收到的最高確認和發送窗口大小的限制。
發送方操做
每當發送方具備要發送的數據時,它能夠在最新的確認na以前傳輸序列號高達wt數據包。也就是說,只要nt<na + wt,它能夠傳送分組號nt。
在沒有通訊錯誤的狀況下,發送方很快就會收到全部發送的數據包的確認信息,這等於nt。若是在合理的延遲以後不會發生這種狀況,則發送方必須在na和nt之間重傳數據包。
接收方操做
每次接收到一個編號爲x的數據包時,接收方檢查它是否落入接收窗口,nr≤x<ns + wr。 (最簡單的接收方只須要跟蹤一個值nr =ns。)若是它落在窗口內,接收方接受它。若是編號爲nr,則接收序列號增長1,而且若是先前接收和存儲更多的連續分組,則可能更多。若是x> nr,則存儲數據包直到接收到全部先前的數據包爲止。若是x≥ns,後者更新爲ns = x + 1。
若是數據包的序列號不在接收窗口內,則接收方將丟棄該數據包,而且不修改nr或ns。不管數據包是否被接受,接收方發送包含當前nr的確認。 (確認還能夠包括關於nr或ns之間接收的附加數據包的信息,但這隻能幫助效率。)
請注意,沒有必要讓接收窗口wr大於發送窗口wt,由於不須要擔憂接收到永遠不會發送的數據包;有用範圍爲1≤wr≤wt。
滑動窗口協議以基於分組的數據傳輸協議爲特徵。所以該協議適用於對按順序傳送分組的可靠性要求較高的環境,例如在數據鏈路層(OSI模型)以及傳輸控制協議(TCP)中。
[2]
加強應答的鏈路層重傳,在長線傳輸中,因軟故障形成的消息傳輸錯誤佔據了絕大部分,對於這些問題的解決能夠是系統的可靠性大大提升。這種機制,向經過實現簡單、檢突發錯誤能力高的CRC碼的校驗進行錯誤檢查,再由相應的滑動窗口協議實現重傳恢復。
[3]
[1] 中止等待協議(stop-and-wait)
中止等待協議示意圖
這時接受方的窗口和發送方的窗口大小都是1,1個比特就夠表示了,因此也叫1比特滑動窗口協議。發送方這時天然發送每次只能發送一個,而且必須等待這個數據包的ACK,才能發送下一個。雖然在效率上比較低,帶寬利用率明顯較低,不過在網絡環境較差,或是帶寬自己很低的狀況下,仍是適用的。
存在的問題是,當發送方交替發送標記爲「奇數」和「偶數」的數據包。 發送的確認一樣爲「奇數」和「偶數」。 假設已經發送了奇數分組的發送方沒有收到奇數確認,而是當即發送下一個偶數分組,在此以後它可能會收到一個確認,爲「下一個奇數包」。這將使發送方出現不肯定因素:接收方有可能接收到這兩個數據包,或者二者都沒接收到。
[2]回退n步協議(GO-BACK-N)
回退N-步協議示意圖
因爲中止等待協議效率過低,所以有了回退n-步協議,這也是滑動窗口協議真正的用處,這裏發送的窗口大小爲n,接受方的窗口仍然爲1。具體看下面的圖,這裏假設n=9: 首先發送方一口氣發送10個數據幀,前面兩個幀正確返回了,數據幀2出現了錯誤,這時發送方被迫從新發送2-8這7個幀,接受方也必須丟棄以前接受的3-8這幾個幀。 後退n協議的好處無疑是提升了效率,可是一旦網絡狀況糟糕,則會致使大量數據重發,反而不如上面的停等協議。
存在的問題在於,假設咱們使用3位序列號,這是
HDLC的典型值。 這使得N =
= 8。 因爲wr = 1,咱們必須限制wt≤7。 這是由於在發送7個數據包以後,有8個可能的結果:0到7個數據包均可能被成功地接收。 這有8種可能性,發送方在確認中須要足夠的信息來區分它們。若是發送方發送8個數據包而不等待確認,則可能會發現本身存在和中止等待協議同樣的問題:這意味着全部8個數據包均可能被成功接收,亦或是一個都沒有被成功接收。
[3]選擇重傳協議(selective repeat)
後退n協議的另一個問題是,當有錯誤幀出現後,老是要重發該幀以後的全部幀,毫無疑問在網絡不是很好的狀況下會進一步惡化網絡情況。
重傳協議即是用來解決這個問題。原理也很簡單,接收端總會緩存全部收到的幀,當某個幀出現錯誤時,只會要求重傳這一個幀,只有當某個序號後的全部幀都正確收到後,纔會一塊兒提交給高層應用。重傳協議的缺點在於接受端須要更多的緩存。
存在的問題在於:最爲廣泛的HDLC協議使用3位序列號,並具備選擇性重複的可選條件。可是,若是使用選擇性重複,則必須保持nt +nr≤8的要求;若是wr增長到2,則wt必須下降到6。假設wr = 2,可是與wt = 7一塊兒使用未修改的發射機。進一步假設接收器以nr = ns = 0開始。
如今假設接收器看到如下一系列數據包(均爲模8):
0 1 2 3 4 5 6(暫停)0
因爲wr = 2,接收方將接受並存儲最終的數據包0(在系列中認爲它是數據包8),同時請求重發數據包7。.然而,發送方也不可能接收到任何確認,而且在後一種狀況下,接收機將接收錯誤的分組做爲分組8。解決方案是發送方限制wt≤6。經過這種限制,接收方在接收到分組6後知道發送方的na≥1,而且所以編號爲0的後續分組必須是分組8。若是全部確認丟失,則發送方將不得不在分組5以後中止。
(1)發送方沒必要發送一個全窗口大小的數據。
(2)來自接收方的一個報文段確認數據並把窗口向右邊滑動,這是由於窗口的大小是相對於確認序號的。
(3)窗口的大小能夠減少,可是窗口的右邊沿卻不可以向左移動。
(4)接收方在發送一個ACK前沒必要等待窗口被填滿。
因爲「滑動窗口」協議的性能取決於窗口大小和網絡接收數據包的速度,在流量不穩定的環境中,性能降低甚至可能會使網絡發生衝突。 爲了不和提供端到端流量控制,能夠建議「慢啓動」協議。
對於該協議的改進主要集中在如何減小TCP報文重傳方面,目前在TCP中每傳輸一個報文都要求接收方進行確認,大量短而頻繁的確認報文給網絡帶來了不少開銷。所以採起了延遲ACK策略來減小ACK的數量,就是接收方收到一個報文之後,不會當即發送ACK,而是等待1~200ms,這期間如有回送數據報文就捎帶確認,但收到兩個連續數據報文或者等待超時則發送一個獨立確認。有效減小了ACK的數量,改善了TCP的總體性能。
[4]
TCP滑動窗口機制html
![TCP滑動窗口機制](http://static.javashuo.com/static/loading.gif)
咱們能夠大概看一下上圖的模型:編程
首先是AB之間三次握手創建TCP鏈接。在報文的交互過程當中,A將本身的緩衝區大小(窗口大小)3發送給B,B同理,這樣雙方就知道了對端的窗口大小。緩存
A開始發送數據,A連續發送3個單位的數據,由於他知道B的緩衝區大小。在這一波數據發送完後,A就不能再發了,需等待B的確認。服務器
A發送過來的數據逐漸將緩衝區填滿。網絡
這時候緩衝區中的一個報文被進程讀取,緩衝區有了一個空位,因而B向A發送一個ACK,這個報文中指示窗口大小爲1。框架
A收到B發過來的ACK消息,而且知道B將窗口大小調整爲1,所以他只發送了一個單位的數據而且等待B的下一個確認報文。socket
如此反覆。分佈式
什麼是滑動窗口協議?
一圖勝千言,看下面的圖。簡單解釋下,發送和接受方都會維護一個數據幀的序列,這個序列被稱做窗口。發送方的窗口大小由接受方肯定,目的在於控制發送速 度,以避免接受方的緩存不夠大,而致使溢出,同時控制流量也能夠避免網絡擁塞。下面圖中的4,5,6號數據幀已經被髮送出去,可是未收到關聯的 ACK,7,8,9幀則是等待發送。能夠看出發送端的窗口大小爲6,這是由接受端告知的(事實上必須考慮擁塞窗口cwnd,這裏暫且考慮 cwnd>rwnd)。此時若是發送端收到4號ACK,則窗口的左邊緣向右收縮,窗口的右邊緣則向右擴展,此時窗口就向前「滑動了」,即數據幀10 也能夠被髮送。函數
![點擊看大圖](http://static.javashuo.com/static/loading.gif)
下面就滑動窗口協議作出更詳細的說明,這裏爲了簡單起見設定發送方窗口大小爲2,接受方大小爲1。看下面圖:post
一:初始態,發送方沒有幀發出,發送窗口先後沿相重合。接收方0號窗口打開,等待接收0號幀;
二:發送方打開0號窗口,表示已發出0幀但尚確認返回信息。 此時接收窗口狀態不變;
三:發送方打開0、1號窗口,表示0、1號幀均在等待確認之列。至此,發送方打開的窗口數已達規定限度,在未收到新的確認返回幀之 前,發送方將暫停發送新的數據幀。接收窗口此時狀態仍未變;
四:接收方已收到0號幀,0號窗口關閉,1號窗口打開,表示準備接收1號幀。此時發送窗口狀態不 變;
五:發送方收到接收方發來的0號幀確認返回信息,關閉0號窗口,表示從重發表中刪除0號幀。此時接收窗口狀態仍不變
六:發送方繼續發送2號幀,2號窗口 打開,表示2號幀也歸入待確認之列。至此,發送方打開的窗口又已達規定限度,在未收到新的確認返回幀以前,發送方將暫停發送新的數據幀,此時接收窗口狀態 仍不變;
七:接收方已收到1號幀,1號窗口關閉,2號窗口打開,表示準備接收2號幀。此時發送窗口狀態不變;
八:發送方收到接收方發來的1號幀收畢的確認信 息,關閉1號窗口,表示從重發表中刪除1號幀。此時接收窗口狀態仍不變。
1比特滑動窗口協議?
上面說的只是滑動窗口協議的理論,實際應用中又有不一樣。首先就是停等協議(stop-and-wait),這時接受方的窗口和發送方的窗口大小都是1,1 個比特就夠表示了,因此也叫1比特滑動窗口協議。發送方這時天然發送每次只能發送一個,而且必須等待這個數據包的ACK,才能發送下一個。雖然在效率上比 較低,帶寬利用率明顯較低,不過在網絡環境較差,或是帶寬自己很低的狀況下,仍是適用的。看下面的流程圖:
![pic304 pic304](http://static.javashuo.com/static/loading.gif)
後退n協議?
停等協議雖然實現簡單,也能較好的適用惡劣的網絡環境,可是顯然效率過低。因此有了後退n協議,這也是滑動窗口協議真正的用處,這裏發送的窗口大小爲n,接受方的窗口仍然爲1。具體看下面的圖,這裏假設n=9:
首先發送方一口氣發送10個數據幀,前面兩個幀正確返回了,數據幀2出現了錯誤,這時發送方被迫從新發送2-8這7個幀,接受方也必須丟棄以前接受的3-8這幾個幀。
後退n協議的好處無疑是提升了效率,可是一旦網絡狀況糟糕,則會致使大量數據重發,反而不如上面的停等協議,實際上這是很常見的,具體能夠參考TCP擁塞控制。
選擇重傳協議?
後退n協議的另一個問題是,當有錯誤幀出現後,老是要重發該幀以後的全部幀,毫無疑問在網絡不是很好的狀況下會進一步惡化網絡情況,重傳協議即是用來解 決這個問題。原理也很簡單,接收端總會緩存全部收到的幀,當某個幀出現錯誤時,只會要求重傳這一個幀,只有當某個序號後的全部幀都正確收到後,纔會一塊兒提 交給高層應用。重傳協議的缺點在於接受端須要更多的緩存。
TCP滑動窗口易錯處
前段時間研究分佈式時寫了一個可擴展的服務器組程序,服務器組之間通訊時總是達不到想要的性能。後來抓包排查,原來是TCP滑動窗口引發的問題,原本是很基礎的東西,奈何當初沒有太在乎,致使錯誤的產生,如今詳細寫出來,忘不太清楚者警戒!
滑動窗口的基本狀況我有必要廢話一下。TCP通訊爲了保證可靠性,每次發送的數據都須要獲得對方的ACK才確認對方收到了(僅保證對方TCP接收緩衝收到數據了,但不保證對方應用程序取到數據了),這時若是每次發送一次就要停下來等着對方的ACK消息,顯然是一種極大的資源浪費和低下的效率,這時就有了滑動窗口的出現。
發送方的滑動窗口維持着當前發送的幀序號,已發出去幀的計時器,接收方當前的窗口大小(由接收方ACK通知,大致等於接收緩衝大小-未處理的消息包),接收方滑動窗口保存的有已接收的幀信息、期待的下一幀的幀號等,至於滑動窗口的具體工做原理這裏就不說了。
一個socket有兩個滑動窗口(一個sendbuf、一個recvbuf),兩個窗口的大小是經過setsockopt函數設置的,如今問題就出在這裏,經過抓包顯示,設置的窗口大小沒有生效,最後排查發現setsockopt函數是後來加上的,寫到了listen函數的後面,這樣每次accept出的socket並無繼承獲得主socket設置的窗口大小,無語啊……
解決辦法:setsockopt函數提早到listen函數以前,這樣在服務器程序啓動監聽前recvbuf就已經有了,accept後的連接獲得的就是recvbuf了,啓動程序運行,抓包顯示窗口已是指定的大小了。
網絡編程其實很簡單,任何人均可以寫出一套本身的服務器框架,可是細節決定成敗,性能的高低有時候就是幾個小細節決定的(固然這裏說的這個問題是個編程錯誤,不屬於可優化的細節問題)
前言
你如今的努力,是爲了之後有更多的選擇。
在上一篇文章經過「表白」方式,讓咱們快速瞭解網絡七層協議瞭解了網絡七層協議。
接下來咱們要把重心放在網絡傳輸的可靠性上面。一塊兒來看TCP協議,它是如何解決網絡傳輸不可靠的問題。這其中有個很關鍵的部分,就是咱們的滑動窗口協議。
從工程學角度上,咱們來看一看滑動窗口協議,它到底解決了一個怎樣的問題?
滑動窗口協議:
- TCP協議的使用
- 維持發送方/接收方緩衝區
緩衝區是 用來解決網絡之間數據不可靠的問題,例如丟包,重複包,出錯,亂序
在TCP協議中,發送方和接受方經過各自維護本身的緩衝區。經過商定包的重傳機制等一系列操做,來解決不可靠的問題。
問題一:如何保證次序?
提出問題:在咱們滑動窗口協議以前,咱們如何來保證發送方與接收方之間,每一個包都能被收到。而且是按次序的呢?
發送方發送一個包1,這時候接收方確認包1。發送包2,確認包2。就這樣一直下去,知道把數據徹底發送完畢,這樣就結束了。那麼就解決了丟包,出錯,亂序等一些狀況!同時也存在一些問題。問題:吞吐量很是的低。咱們發完包1,必定要等確認包1.咱們才能發送第二個包。
問題二:如何提升吞吐量?
提出問題:那麼咱們就不能先連發幾個包等他一塊兒確認嗎?這樣的話,咱們的速度會不會更快,吞吐量更高些呢?
如圖,這個就是咱們把兩個包一塊兒發送,而後一塊兒確認。能夠看出咱們改進的方案比以前的好不少,所花的時間只是一個來回的時間。接下來,咱們還有一個問題:改善了吞吐量的問題
問題三:如何實現最優解?
問題:咱們每次須要發多少個包過去呢?發送多少包是最優解呢?
咱們能不能把第一個和第二個包發過去後,收到第一個確認包就把第三個包發過去呢?而不是去等到第二個包的確認包纔去發第三個包。這樣就很天然的產生了咱們"滑動窗口"的實現。
在圖中,咱們可看出灰色1號2號3號包已經發送完畢,而且已經收到Ack。這些包就已是過去式。四、五、六、7號包是黃色的,表示已經發送了。可是並無收到對方的Ack,因此也不知道接收方有沒有收到。八、九、10號包是綠色的。是咱們尚未發送的。這些綠色也就是咱們接下來立刻要發送的包。 能夠看出咱們的窗口正好是11格。後面的11-16尚未被讀進內存。要等4號-10號包有接下來的動做後,咱們的包纔會繼續往下發送。
正常狀況
能夠看到4號包對方已經被接收到,因此被塗成了灰色。「窗口」就往右移一格,這裏只要保證「窗口」是7格的。 咱們就把11號包讀進了咱們的緩存。進入了「待發送」的狀態。八、9號包已經變成了黃色,表示已經發送出去了。接下來的操做就是同樣的了,確認包後,窗口日後移繼續將未發送的包讀進緩存,把「待發送「狀態的包變爲」已發送「。
丟包狀況
有可能咱們包發過去,對方的Ack丟了。也有可能咱們的包並無發送過去。從發送方角度看就是咱們沒有收到Ack。
發生的狀況:一直在等Ack。若是一直等不到的話,咱們也會把讀進緩存的待發送的包也一塊兒發過去。可是,這個時候咱們的窗口已經發滿了。因此並不能把12號包讀進來,而是始終在等待5號包的Ack。
若是咱們這個Ack始終不來怎麼辦呢?
超時重發
這時候咱們有個解決方法:超時重傳
這裏有一點要說明:這個Ack是要按順序的。必需要等到5的Ack收到,纔會把6-11的Ack發送過去。這樣就保證了滑動窗口的一個順序。
這時候能夠看出5號包已經接受到Ack,後面的六、七、8號包也已經發送過去已Ack。窗口便繼續向後移動。
參考:TCP 滑動窗口協議
參考:滑動窗口協議
參考:一篇帶你讀懂TCP之「滑動窗口」協議