1. 程式人生 > >TCP協議學習總結(下)

TCP協議學習總結(下)

out 窗口 我們 中間 strong 以及 簡單 就是 bsp

在前兩邊TCP學習總結中,也大概地學習了TCP的整個流程,但許多細節中的細節並沒有詳細學習,例如超時重傳問題,每次瓶頸回歸慢啟動效率問題以及最大窗口限制問題等。本學習篇章最要針對這些細節中的細節進行學習。TCP的復雜很多時候就是細節太多了,需要考慮許多的場景並利用許多復雜的算法和啟動異步線程定時處理這些問題,對於每一個連接,TCP管理4個不同的定時器,分別是:

1)、重傳定時器使用於當希望收到另一端的確認;

2)、堅持定(persist)時器使窗口大小信息保持不斷流動,即使另一端關閉了其接收窗口;

3)、保活(keepalive)定時器可檢測到一個空閑連接的另一端何時崩潰或重啟;

4)、2MSL定時器測量一個連接處於TIME_WAIT狀態的時間。

TCP的超時與重傳

RTO(Retransmission TimeOut)算法

TCP計算超時有一個叫做“指數退避”的算法,例如如果發送方在發送數據的時候,服務端突然關閉服務時,客戶端是會根據初始超時時間開始一步步指數的增長超時重傳時間,如下圖所示:

技術分享圖片

以上簡單例子的第一次超時重傳時間是1.5秒,第二次是3秒,第三次是6秒以此指數增長類推,直到最大超時時間64秒後就不再增長,直到9分鐘(TCP實現中不可變)後客戶端直接發送一個RST報文段斷開連接。由於路由器和網絡流量均會變化,因此TCP會跟蹤這些變化並相應地改變其超時時間,所以超時的初始化時間不是固定的。它會根據報文段的往返時間(RTT)去評估,具體算法如下:

最初算法:R=αR+(1-α)M ;RTO=Rβ;

R:RTT估計器;

M:當前測量值;

α:是一個推薦值為0.9的平滑因子;

β:是一個推薦值為2的時延離散因子。

從以上算法可以看出,每個新估計的90%來自於前一個估計,而10%則取自新的測量。[Jacobson 1988]詳細分析了在RTT變化範圍很大時,使用這個方法無法跟上這種變化,從而引起不必要的重傳。所以Jacobson建議除了被平滑的RTT估計器(R),所需要做的還有跟蹤RTT的方差。在往返時間變化起伏很大時,基於均值和方差來計算RTO,將比作為均值的常數倍來計算RTO能提供更好的響應。正如Jacobson所描述的,均值偏差是對標準差的一種好的逼近,但卻更容易進行計算(計算標準方差需要一個平方根)。這就引出了下面用於每個RTT測量M的公式:

Err=M-A

A←A+gErr

D←D+h(|Err|-D)

RTO=A+4D

這裏的A是被平滑的RTT(均值的估計器),而D則是被平滑的均值偏差。Err是剛得到的測量結果與當前的RTT估計器之差。A和D均被用於計算下一個重傳時間(RTO)。增量g起平均作用,取為1/8(0.125)。偏差的增益是h,取值為0.25.當RTT變化時,較大的偏差增益將使RTO快速上升。其實重傳測量結果還會存在一個問題,就是發生重傳時,然後收到一個確認,那麽這個ACK是針對第一個分組還是針對第二個分組呢?這就是所謂的重傳多義性問題。

Karn算法

這裏又涉及到一個新的算法,叫“Karn算法”。他規定,當一個超時和重傳發生時,在重傳數據的確認最後到達時,不能更新RTT估計器,因為我們並不知道ACK是針對哪一次傳輸。並且,由於數據被重傳,RTO已經得到了一個指數退避,我們在下一次傳輸時使用這個退避後的RTO,對於一個沒有被重傳的報文段而言,除非收到了一個確認,否則不要計算新的RTO。

擁塞避免算法

該算法假定由於分組受到損壞引起丟失是非常少的(遠小於1%),因此分組丟失就意味著在源主機和目的主機之間的某處網絡上發生了擁塞。有兩種分組丟失的指示:發生超時和接收到重復的確認(接收端強調我只接受到這個報文而已)。在上一學習總結中學到了一個叫“慢啟動”的限流算法,也拋出了一個老是慢啟動也不是辦法的問題。擁塞避免算法和慢啟動算法是兩個目的不同、獨立的算法。但是當擁塞發生時,我們希望降低分組進入網絡的傳輸速率,於是可以調用慢啟動來做到這一點。所以很多時候,在實際中這兩個算法通常在一起實現。

擁塞避免算法和慢啟動算法需要對每個連接維持兩個變量:一個擁塞窗口cwnd和一個慢啟動門限ssthresh。這樣得到的算法的工作過程如下:

1)、對於一個給定的連接,初始化cwnd為1個報文段,ssthresh為65535個字節(註意:這是最大窗口大小);

2)、TCP輸出例程的輸出不能超過cwnd和接收方通告窗口的大小。擁塞避免是發送方使用的流量控制,而通告窗口則是接收方進行的流量控制。前者是發送方感受到的網絡擁塞的估計,而後者則與接收方在該連接上的可用緩存大小有關。

3)、當擁塞發生時(超時或收到重復確認),ssthresh被設置為當前窗口大小的一半(cwnd和接收方通告窗口大小的最小值,但最少為2個報文段)。此外,如果是超時引起了擁塞,則cwnd被設置為1個報文段(這就是慢啟動);

4)、當新的數據被對方確認時,就增加cwnd,但增加的方法依賴於我們是否正在進行慢啟動或擁塞避免。如果cwnd小於或等於ssthresh,則正在進行慢啟動,否則正在進程擁塞避免。慢啟動一直持續到我們回到當擁塞發生時所處位置一半的時候才停止(因為我們記錄了在步驟2中給我們制造麻煩的窗口大小的一半),然後專為執行擁塞避免。

技術分享圖片

在該例子當中,假定cwnd為32個報文段時就發生擁塞,於是設置shthresh為16個報文段,而cwnd為1個報文段。在時刻0發送了一個報文段,並假定在時刻1接收到它的ACK,此時cwnd為2。接著發送了2個報文段,並假定在時刻2接收到它的ACK,於是cwnd增加為4(對於每個ACK增加1次)。這種指數增加算法一直進行到時刻3和4之間收到8個ACK後cwnd等於ssthresh時才停止,從該時刻開始,cwnd以線性方式增加,在每個往返時間內最多增加1個報文段。從例子可以看出,慢啟動與擁塞避免算法的合並使用效果。那麽它們的合作是如何避免每次都要重慢啟動開始呢?

快速重傳與快速回復算法

算法過程如下:

1)、當收到3個重復的ACK時(1~2個ACK無法確認是丟失),將ssthresh設置為當前擁塞窗口cwnd的一半。重傳丟失的報文段,設置cwnd為ssthresh加上3倍的報文段大小(因為收到3個重復ACK)。

2)、每次收到另一個重復ACK時,cwnd增加1個報文段大小並發送1個分組(如果新的cwnd允許發送)。

3)、當下一個確認新數據的ACK到達時,設置cwnd為ssthresh(在第1步中設置的值)。這個ACK應該是在進行重傳後的一個往返時間內對步驟1中重傳的確認。另外,這個ACK也應該是對丟失的分組和收到的第1個重復ACK之間的所有中間段報文的確認。這一步采用的是擁塞避免,因為當分組丟失時我們將當前的速率減半。

說白了就是通過3次重復ACK確認報文的丟失而不是等待超時定時器溢出的確認,這樣就避免了慢啟動算法的激活,而從最低初始化擁塞窗口(1個報文段)起發送。

TCP的堅持定時器

上一篇學習總結中提到過通告窗口為0時發送方將停止發送數據的問題。為了避免發送方和接收方的死循環等待,發送方使用一個“堅持定時器(presist timer)”來周期性地向接收方查詢,以便發現窗口是否已經增大。這些從發送方發出的報文段稱為窗口查探(window probe)。

技術分享圖片

從上圖可以看出,堅持定時器使用了普通的TCP指數退避。對於一個典型的局域網連接,首次超時時間算出來是1.5秒,第2次的超時時值增加一倍,為3秒,再下次乘以4為6秒,之後再乘以8為12秒等,但是堅持定時器總是在5~60秒之間。窗口探查包含一個字節的數據(以上例子序號為4098)。TCP總是允許發送已關閉窗口之後一個字節的數據。堅持狀態與重傳超時之間一個不同的特點就是TCP從不放棄發送窗口探查(重傳超時會持續9分鐘),這些探查每隔60秒發送一次,這個過程將持續到或者窗口被打開或應用進程使用的連接被終止。

在上一篇總結當中同樣提到了一個叫“糊塗窗口綜合征”的問題。也就是說,接收方每出現哪怕1個字節的空閑窗口也會通告發送方,那麽發送放就會立刻發送數據,但就會讓發送方與接收方陷入“小分組”發送的死循環,從而會造成網絡擁塞問題。該現象可以發生在兩端的任何一端,接收方可以通告一個小的窗口(而不是一直等到有大的窗口時才通知),而發送方也可以發送少量數據(而不是等待其他的數據以便發送一個大的報文段)。可以在任何一端采取措施避免出現“糊塗窗口綜合征”的現象:

1)、接收方不通告窗口大小。通常的算法是接收方不通告一個比當前窗口大的窗口(可以為0)除非窗口可以增加一個報文段大小(也就是將要接受的MSS)或者可以增加接收方緩存空間的一半,不論實際有多少;

2)、發送方避免出現“糊塗窗口綜合征”的措施只有以下條件之一滿足時才發送數據:(a)可以發送一個滿長度的報文段;(b)可以發送至少是接收方通告窗口大小一半的報文段;(c)能夠發送手頭的所有數據並且不希望接收ACK(也就是說,我們沒有還未確認的數據)或該連接禁止了Nagle算法。

TCP的保活定時器

保活定時器主要是為服務器應用程序提供的,因為許多時候一個服務器希望知道客戶主機是否崩潰並關機或者崩潰又重新啟動。但有一點需要註意:

保活定時器並不是TCP規範中的一部分。Host Requirements RFC提供了3個不使用保活定時器的理由:(1)在出現短暫差錯的情況下,這可能會使一個非常好的連接釋放掉;(2)它們耗費不必要的帶寬;(3)在按分組計費的情況下會在互聯網上花掉更多的錢。然而,許多實現提供了保活定時器。

如果一個給定的連接在兩個小時之內沒有任何動作,則服務器就向客戶發送一個探查報文段,客戶主機必須處於以下4個狀態之一:

1)、客戶主機依然正常運行,並從服務器可達。客戶的TCP響應正常,而服務器也知道對方是正常工作的。服務器在兩個小時以後將保活定時器復位。如果在兩個小時定時器到時間之前有應用程序的通信量通過此連接,則定時器在交換數據後的未來2小時再復位。

2)、客戶主機已經崩潰,並且關閉或者正在重新啟動。在任何一種情況下,客戶的TCP都沒有響應。服務器將不能夠收到對探查的響應。並在75秒後超時。服務器總共發送10個這樣的探查,每個間隔75秒。如果服務器沒有收到一個響應,它就認為客戶主機已經關閉或終止連接。

3)、客戶主機已經崩潰並已經重新啟動。這是服務器將收到一個對其保活探查的響應,但是這個響應是一個復位,使得服務器終止這個連接。

4)、客戶主機正常運行,但是從服務器不可達。這與狀態2相同,因為TCP不能夠區分狀態4與狀態2之間的區別,它所能發現的就是沒有收到探查的響應。

學習總結

本篇章主要針對TCP管理的4個定時器的介紹,包括超時重傳定時器、堅持定時器、保活定時器,以及第一篇章介紹的2MSL的TIME_WAIT狀態定時器。這些都是確保TCP可靠性的重要手段。

TCP協議學習總結(下)