網絡編程基礎(4)-協議概要-TCP的流量控制

序列號和確認應答

這裏,我們先回頭來看看在TCP首部中簡要介紹過的序列號和確認應答號。

序列號是按順序給發送數據的每一個字節都標上號碼的編號。序列號用來解決網絡包亂序的問題。
這裏寫圖片描述

確認應答號是接收端查詢接收數據TCP首部中的序列號和數據長度(IP首部中的數據包長度-IP首部長度-TCP首部長度),將自己下一步應該接收的序列號作爲確認應答號返送回去。用其來告知是否有丟包的情況發生。
這裏寫圖片描述

交互數據流

TCP通信數據,大致可以分爲兩類,一類是交互數據流,以客戶端和服務器相互發送交互數據爲主,報文長度一般都比較小,兩端皆有數據流動。另一類是類似文件傳輸服務的成塊數據流,報文段基本都是滿長度。數據流向基本是服務器到客戶端單向居多。對於這兩類數據,TCP有不同的處理算法。

延遲確認應答

TCP協議規定在接收到數據段時需要向對方發送一個確認,但如果只是單純的立即發送一個確認,會降低網絡利用率,而且可能會返回一個較小的窗口(引起糊塗窗口綜合症,後文講解)。所以TCP在何時發送ack給對方有以下規定:

1:當有響應數據要發送時,ack會隨響數據立即發送給對方,這也是常說的數據捎帶ACK。

2:如果沒有響應數據,ack的發送將會有一個延遲,以等待看是否有響應數據可以一起發送。但這個延遲最多不會超過500ms(否則可能引起對端重傳),一般爲200ms。如果在200ms內有數據要發送,那麼ack會隨數據一起立即發送給對方。這裏的延遲200ms,不是指的從接收到對方數據到發送ack的最長等待時間差。而是指的內核啓動的一個定時器,它每隔200ms就查看下是否有ack要發送。例如:假設定時器在0ms時啓動,對方的數據段在185ms時到達,那麼ack最遲會在200ms時發送,而不是385ms時發送。

3:如果在等待發送ack期間,對方的第二個數據段又到達了,這時要立即發送ack。但是如果對方的三個數據段相繼到達,那麼第二個數據段到達時ack立即發送,但第三個數據段到達時是否立即發送,則取決於上面兩條。

交互數據流總是以小於最大報文段長度的分組發送。對於這些小報文段,接收方延遲確認應答來判斷是否可被推遲發送,以便與回送數據一起發送。這樣通常會減少報文段數目。

Nagle算法

廣域網上傳輸大量小分組(用戶數據很少的報文)時,會減少網絡利用率,還可能會增加擁塞的概率。Nagle算法是針對此問題的解決辦法。

具體來說該算法是一種延遲發送數據的處理機制,當下列任意條件達成時纔可發送數據。否則,暫時等待一段時間(在這段時間內收集合並這些小分組)後再進行數據傳送。
條件1:已發送的數據都已經收到確認應答。
條件2:可以發送最大段長度的數據時。

該算法是自適應的,確認到達的越快,數據也就發送得越快。雖然可以提高網絡利用率,但是和延遲確認應答相互作用可能會引發某種程度的延遲。一般局域網很少使用這個算法,在窗口和機械控制領域中使用TCP,往往會關閉該算法。

成塊數據流

前面說到Nagle算法通常用在較慢的廣域網中減少小分組數目。但是對於像文件傳輸這種大多數都是達到MSS長度的分組,如果發送一個段,停止等待一個確認,顯然會降低傳輸效率。對此,TCP使用滑動窗口協議,它允許發送方在等待一個確認前可以連續發送多個分組,只要這些連續的分組總長度不大於接收方告知的窗口大小。

滑動窗口協議

滑動窗口實例

TCP首部中有一個字段叫做窗口大小。這個字段是接收端告訴發送端自己還有多少緩衝區可以接收數據。於是發送端就可以根據這個接收端的處理能力來發送數據,而不會導致接收端處理不過來。下圖是一個TCP通信實例圖(出自《TCPv1:協議》):

這裏寫圖片描述

報文段1、2、3對應TCP三次握手連接過程,連接雙方相互告知MSS,窗口大小信息。
發送方首先傳送4、5、6三個數據報。從報文段7的ack值2049可以看出僅確認了4、5兩個報文段,隨後報文段8確認了報文段6。這一過程可以對照前面延遲確認應答的第3點理解。報文段8中的窗口大小爲3072,這說明,TCP的接收緩存中還有1024個字節數據等待被應用程序讀取。
報文段11-16說明通常會使用“隔一個報文段確認”的策略。如報文段14確認報文段11和12,報文段13確認報文段13和15。這是因爲使用滑動窗口協議時,接收方不必確認每一個收到的分組,在TCP中,ack是累積的,表示接收方已經正確收到了確認應答號減1的所有字節。
報文段17-20對應TCP四次揮手斷開連接。

這裏注意一下上圖中的PSH。表明此報文段是設置了PUSH標誌位的。該標誌位表明發送方通知接收方將所有收到的數據全部提交給應用進程。這裏的數據包括與PUSH一起傳送的數據及接收方TCP已經爲接收進程收到的其他數據。大多數TCP的實現能夠自行決定何時設置這個標誌。

現在用可視化的方法展示滑動窗口協議。
這裏寫圖片描述
上圖中,將字節從1到11編號,接收方通告的窗口稱爲提供的窗口,覆蓋第4到第9的區域,表明接收方已確認了第4字節之前的數據,且通告的窗口大小爲6。發送計算可用窗口,該窗口表明多少數據可以立即發送。
當接收方確認數據後,這個窗口不時的向右移動。窗口的兩個邊沿的相對運動增大或減小了窗口大小。
1:窗口左邊沿向右邊靠近,稱爲窗口合攏。數據被髮送和確認時發生該現象。
2:窗口右邊沿向右邊移動,稱爲窗口張開。允許發送更多的數據,當另一端的讀取已經確認的數據並釋放TCP的接收緩存時發生該現象。
3:窗口右邊沿向左移動,稱爲窗口收縮。RFC強烈建議不使用這種方式。

下圖展示了上面通信實例圖數據傳輸過程中滑動窗口協議的動態性。
這裏寫圖片描述

窗口大小的確定

無論如何窗口大小不可能是一個隨機數,它有自己的計算準則,其中一個計算規則如下圖所示:
這裏寫圖片描述
這也稱爲帶寬時延乘積。
根據上圖公式所計算出的窗口大小爲:
WS=10,000,000x0.01=>WS=100,000bitsor(100,000/8)/1024=12,5Kbytes
按照這種方式計算出的窗口大小理論上(慢速廣域網上帶寬是共享而非獨佔,所以這是在理論上的)是最有效率的,因爲發送端可以連續發送多個段來填滿這個管道,形成最大吞吐量。

窗口擴大選項

上圖計算的窗口大小爲125Kb,而TCP首部中窗口大小字段爲16位,最大表示64Kb的窗口。那如何表示125Kb窗口呢?TCP首部選項中有個窗口擴大選項,使用該選項可以使得發送端得到更大的通告窗口,這樣就可以發送更多的數據,提高數據傳輸效率。

窗口擴大選項佔3字節(請結合TCP首部一節),其中有一個字節表示移位值S。新的窗口值等於TCP首部中的窗口位數以16增大到(16+S)。移位值允許使用的最大值是14,相當於窗口最大值增大到2^(16+14)-1 = 2^30 - 1。

該選項只能出現在SYN報文中,因此當連接建立起來後,每個方向上的擴大因子是固定的。主動建立連接的一方在其SYN中發送這個選項,而被動建立連接的一方只能夠在收到帶有這個選項的SYN後纔可以發送這個選項。每個方向上的擴大因子可以不同。但是被動連接方如果回覆報文段中沒有這個選項,那麼這個連接將不使用窗口擴大,這是爲了向後兼容,因爲較老的系統可能不支持該選項。

堅持定時器

TCP通過滑動窗口進行流量控制,如果窗口爲0,這會一種什麼情況。下圖展示了一個從快的發送方和慢的接收方的通信實例。
這裏寫圖片描述
發送方連續發送4-7四個報文段去填充對端的窗口,然後等待ACK。報文段8表示接收方發送ACK,但是通告窗口卻爲0,說明接收方已經收到所有數據,但是這些數據還在TCP緩衝區。發送方得知接收方窗口爲0,它將不發送任何數據。少許時間後,接收方發送報文段9,窗口大小4096。此報文段並不確認數據,只是通告窗口大小,因此被稱爲窗口更新。隨後獲知窗口大小的發送方打開窗口並繼續發送數據…

然而實際情況中,窗口更新(報文段9)是有可能丟失的。如果丟失,那發送方會一直傻等嗎?並不會。發送方會使用一個堅持定時器來週期性地向接收方發送1個字節的窗口探測報文,以便發現窗口是否已經增大。TCP從不放棄發送窗口探測,這個過程持續到窗口打開或者連接被終止。
在上圖中,發送方接收到報文段8,得知窗口爲0從而設置堅持定時器,在堅持定時器溢出前,接收方發來了窗口更新,於是發送方繼續傳輸數據。但如果在堅持定時器溢出前,發送方並沒有收到窗口更新,那麼它將發送一個1字節窗口探測報文並再次設置堅持定時器。

糊塗窗口整合症

避免措施

基於窗口的流量控制可能會出現“糊塗窗口整合症”的情況。接收方通告一個小窗口(而不是一直等到有大窗口時才通告),而發送方可以發送少量數據(而不是等待其它的數據以便一次發送一個大的報文段)。這樣的情況就會造成傳輸的分組增多,降低網絡利用率。可以採取以下措施避免。
1:如果這個問題由接收方引起,那麼接收方不通告小窗口。只有當緩衝區增加不小於MSS大小或增加不小於接收緩存一半大小的時候,它才發送一個非0窗口通告,否則回送一個0窗口通告。
2:如果這個問題由發送方引起,那麼就使用Nagle算法。

實例

我們來看一個避免出現糊塗窗口的實例,該例子也包含了堅持定時器和延遲確認應答。描述該例需要兩個圖,一個是時間系列圖:
這裏寫圖片描述
一個是事件序列圖:
這裏寫圖片描述

我們對照兩圖來描述。
發送方發送1-4報文段去填充滿接收方的緩衝區。接收方回覆報文段5,確認數據並通知一個0窗口。發送方此時設置堅持定時器,大概5秒後堅持定時器溢出,此時發送一個1字節的窗口探測(報文段6),該字節被接收並確認(報文段7)了。原因是在3.99時刻應用進程已經從TCP緩衝區中讀取了256字節,使得有了可用的接收緩衝區。然而報文段7卻通告了0窗口,這又是因爲仍然沒有足夠的空間來接收一個滿長度的報文,或者不能騰出緩存空間的一半。這就是接收方的糊塗窗口避免措施。
發送方的堅持定時器被複位,並在5秒後再次溢出(在時刻10.151)。然後又發送一個字節並被確認(報文段8和9),而接收方的緩存空間還不夠用(1022字節,小於一個MSS),使得通告窗口爲0。
發送方的堅持定時器在時刻15.151再次溢出,於是又發送了另一個字節並被確認(報文段10和11)。這一次由於接收方有1533字節的有效緩存空間,因此通告了一個非0窗口。發送方立即利用這個窗口發送了1024字節的數據(報文段12)。對這1024字節數據的確認(報文段13)通告其窗口爲509字節。前面說過發送方應避免通告小窗口,這裏爲何可以發送一個小於MSS的窗口?如果這裏按照接收方避免糊塗窗口的措施而發送一個0窗口的話,那會使窗口右邊沿向左移動而造成窗口收縮,前面說過,RFC強烈建議不使用窗口收縮。
接下來我們看到發送方沒有立即向這個小窗口發送數據。這就是發送方採取的糊塗窗口避免策略。相反,它等待堅持定時器在時刻20.151溢出,並在該時刻發送509字節的數據。儘管它最終還是發送了一個長度爲509字節的小數據段,但在發送前它等待了5秒鐘,看是否會有一個ACK到達,以便可以將窗口開得更大。這509字節的數據使得接收緩存僅剩下768字節的有效空間,因此接收方通告窗口爲0(報文段15)。
堅持定時器在時刻25.151再次溢出,發送方發送1個字節,於是接收緩存中有1279字節的可用空間,這就是在報文段17所通告的窗口大小。
發送方只有另外的511個字節的數據需要發送,因此在收到1279的窗口通告後立刻發送了這些數據(報文段18)。這個報文段也帶有FIN標誌。接收方確認數據和FIN,並通告窗口大小爲767字節(因爲FIN要佔用一個字節,所以不是768而是767)。
接收應用進程繼續每隔2秒從接收緩存區中讀取256個字節的數據。爲什麼在時刻39.99發送ACK(報文段20)呢?這是因爲應用進程在時刻39.99讀取數據時,接收緩存中的可用空間已經從原來通告的767(報文段19)變爲2816,這相當於接收緩存中增加了額外的2049字節的空間。因爲現在接收緩存已經增加了其空間的一半,因此接收方現在發送窗口更新。這意味着每次當應用進程從TCP的接收緩存中讀取數據時,接收方的TCP將檢查是否需要更新發送窗口。
注意到報文段5、7、9、11、15、17的發送時刻爲:0.170、5.170、10.170、15.170、20.170、25.17,這是因爲接收方的延遲確認應答機製造成的。

緊急方式

TCP提供“緊急方式”,它使一端可以告訴另一端有些具有某種方式的緊急數據已經放置在普通數據的數據流中。具體的做法是:將TCP首部中URG標誌位置1,並且將16位的緊急指針置爲一個正向偏移量,該偏移量與TCP首部中的序號字段值相加,得出緊急數據的最後一個字節的序號。緊急數據的表示下圖:

這裏寫圖片描述

TCP只通告緊急方式已經開始並指明緊急數據最後一個字節的指針,至於對數據的處理留給應用層程序去做。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章