TCP系列32—窗口管理&流控—6、TCP zero windows和persist timer

一、簡介

    我們之前介紹過,TCP報文中的window size表示發出這個報文的一端準備多少bytes的數據,當TCP的一端一直接收數據,但是應用層沒有及時讀取的話,數據一直在TCP模塊中緩存,最終受限於接收緩存的大小,window size會變爲0,此時我們稱呼這個接收窗口爲零窗(zero window),對端也不能在發送更多的數據。如果隨後本端應用層從TCP接收緩存中讀取了足夠數據,TCP模塊有了足夠的新的接收緩存的時候,就會發送一個TCP報文,並帶有一個有效非零的Window size來指示對端自己已經可以接收新數據了。這個帶有有效Window size的報文我們稱爲窗口更新(window update)報文。窗口更新一般就是一個普通的ACK報文,並不會帶有有效的數據(pure ACK),ACK報文不消耗系列號,如果發生丟失並不會進行重傳。因此TCP需要處理window update消息丟失的場景。

    如果窗口更新報文發生丟失,那麼接收端(這裏的接收端是指window update消息的發送端)會等待發送端發送新的數據,而發送端會等待接收window update消息來發送新的數據,這種場景下,兩端互相等待對方,就會產生一種deadlock(還記得Nagle算法和延遲ACK同時生效的時候也會產生類似的deadlock吧)。爲了阻止這種死鎖一直等待下去,TCP的發送端會使用一個persist timer定時器來定時查詢接收端的window size是否增長,每當這個定時器超時的時候,發送端就會發送window probes報文。接收端在接收到window probe消息的時候會提供一個帶有window size的ACK報文。RFC1122建議初始window probe定時器定時時間爲RTO,隨後進行window probe 的時候應該進行指數回退,最大指數回退次數爲tcp_retries2,如果此時還沒收到有效的window size,則會一直進行window probe過程(我們之前通過示例介紹過RTO超時最後會釋放連接,這個是與window probe的重要區別)。發送端。window probe報文中可以包含1byte的數據也可能不包含數據。當window probe包含數據的時候,接收端可以選擇接收包含的數據也可以選擇不接受包含的數據。關於persist timer,我們前面在介紹cork算法的時候就接觸過了。

    另外在下面的示例中我們將會看到,linux上發出的window probe消息是不帶有有效數據的,而且window probe的系列號位於snd_nxt的前面,linux接收到這種報文的時候會認定這種報文爲無效的系列號。對於這種類型的報文回覆ACK時候受到參數tcp_invalid_ratelimit控制,這個參數控制了TCP對於這類系列號無效的報文的ACK回覆速率,例如下面示例中我們設置tcp_invalid_ratelimit=1200,含義就是說linux對於這類無效報文的ACK回覆間隔最小爲1200ms,只要間隔大於1200ms,linux就會立即回覆一個dup ACK報文,並不受我們之前介紹的延遲ACK策略的影響(延遲ACK一般是針對有效數據來說的)。

二、wireshark示例

1、綜合示例

我們設置tcp_retries2=6,tcp_invalid_ratelimit=1200,通過socket選項SO_RCVBUF設置server端和client端的window size如下圖所示,通過這個示例我們還會進一步說明一下之前介紹過的延遲ACK的處理。

No1-No3:首先client和server端通過三次握手建立TCP連接,其中server端的window size爲3000bytes

接着client端執行一次write操作,一次write寫入5000bytes的數據。

No4:client端內核在從用戶空間讀取數據前會先獲取當前的發送MSS,可以從圖中看到,server端的接收窗口大小爲3000bytes,但是MSS爲65495bytes,顯然不能按照這個MSS來發送,當出現這種情況的時候,linux的發送端(即本例中client端)會取對端最大接收窗口的一半1500bytes爲發送mss。選定MSS後,接着linux內核會從client端嘗試以1500bytes爲單位來複制應用程序的數據(共5000bytes)然後發送出去,No4即對應client發出的第一個數據包。

No5:server端在接收到client端的No4數據包的時候,會初始化quick ACK模式,此時client端的rcv_mss爲1500,rcv_wnd爲3000,rcv_wnd/(2*rcv_mss)=1,因此quick ACK計數器初始化爲1,對No4報文執行quick ACK反饋No5後,quick ACK計數器變爲0,關於延遲ACK的相關內容可以參考前面系列文章

No6:接着client端內核繼續從應用層複製1500bytes的數據併發送出去,此時client端一共發出了3000bytes的數據,而server端應用層一直沒有讀取TCP模塊接收的數據。可以看到wireshark提示TCP Window Full信息。

No7:No5數據包發出去後quick ACK計數器變爲0,此時server端對No6數據包執行延遲ACK策略,定時時間爲40ms。從wireshark可以看到No7與No6數據包實際間隔大約爲38ms,這種定時誤差問題是由於TCP模塊的tick精度問題造成的,前面相關文章已經解釋了,此處不再贅言。server端的3000bytes已經全部被佔用了,此時server端只能回覆Window size爲0的ACK報文,通知client自己不再準備接收新數據了。可以看到wireshark提示了TCP zero Window提示信息。因爲No7延遲了40ms回覆ACK,所以當client收到這個報文的時候,client端的已經完全把應用層的數據複製到了內核中,之前一次write寫入了5000bytes數據,已經發出了3000bytes的數據,此時client端內核中還剩餘2000bytes的數據待發送。client端內核雖然在收到No7報文之前就已經準備好發送數據了,但是由於window size限制而沒發送出去。此時收到No7的ACK後會再次嘗試發送剩餘的2000bytes的數據,但是同樣由於window size限制而發送失敗(如果client忽視window size的限制強制發送,server端會怎麼辦?我們後面文章在用示例來說明),發送失敗後,linux會判斷如果當前已經已經發出的還未收到ACK確認包的報文個數爲0並且還有待發送數據的話就會啓動persist timer定時器,定時時間爲RTO(當前RTO大約爲208ms)。

No8:上面設置的persist timer定時器超時後,強制發送No8報文,注意No8報文的seq實際上比No7報文的ack number小1,而且Len=0,發送完這個報文後設置persist timer定時器,定時時間爲上一次定時時間的2倍(大約爲416ms)。wireshark對於No8報文的提示爲TCP Keep-Alive,實際上這個報文的功能並不是Keep-Alive的功能,後面文章我們會介紹TCP Keep-Alive的。

No9:接着我們看到server端對於No8報文立即回覆了一個No9的ACK確認報文,這裏起作用的並不是quick ACK模式,而是linux對於類似No8這種window probe報文會認爲是無效的系列號,只要當前時間距離上次回覆無效系列號報文的ACK確認包時間超過了tcp_invalid_ratelimit參數設置的時間,那麼linux就會立即回覆ACK確認報文,可以看到這個ACK報文window size仍然爲0。

No10:No8設置的定時器超時後,發出No10的window probe報文,並設置persist timer定時時間爲4*RTO。

可以看到server端收到No10報文後並沒有立即回覆ACK確認包,原因是No10和No9的間隔時間並沒有超過1200ms的ACK發送間隔。

No11:persist timer再次超時,發出No11報文,並重新設置persist timer的定時時間爲8*RTO

No12:server端在收到No11報文的時候,發現這個報文系列號無效,同時距離上一次回覆無效系列號報文ACK確認包的時間(即No9的時間)已經超過了1200ms,因此立即回覆ACK確認包。

No13-No18:這幾個報文重複前面的指數回退過程,server端判斷無效系列號報文的ACK間隔超過1200ms後立即回覆ACK確認包。

No19:client在發送No19的window probe報文的時候發現,前面已經連續發送了No8、No10、No11、No13、No15、No17共6個window probe報文,已經達到了tcp_retries2的配置值,因此隨後client端不在進行指數回退的過程,對persist timer定時器的定時間隔固定爲2^6*RTO,大約爲13.312ms。可以看到這裏沒有釋放TCP連接,而在RTO重傳指數回退過程中,當超過根據tcp_retries2計算的最大重傳時間的時候就會釋放TCP連接。

No20-No30:client端持續進行window probe過程,這個與上面處理類似,不再多說
在這裏插入圖片描述

No31:接着在No30之後server端應用程序完全讀取出TCP中的3000bytes的緩存數據,server端發送window update消息給client端,通知對端可以發送新的數據

No32-No33:client端收到window update後,立即把剩餘的2000byte分兩個數據包發出

No34:server端收到No32報文的時候,發現距離上一次收到有效數據的時間超過了一個RTO,因此進入quick ACK模式,設置quick ACK計數器爲rcv_wnd/(2*rcv_mss)=1,立即回覆ACK確認包報文後,quick ACK計數器減1變爲0

No35:server端在收到No33報文後,此時quick ACK計數器爲0,進入延遲ACK處理,延遲ACK定時器超時後觸發回覆No35的ACK確認包。

在這裏插入圖片描述

補充說明:

1、MSS相對與發送窗口折半的限制處理,請參考tcp_bound_to_half_wnd

2、persist timer的定時器的初始啓動__tcp_push_pending_frames,隨後超時處理tcp_probe_timer

3、linux對於示例中window probe消息的處理以及與參數tcp_invalid_ratelimit的關係,參考tcp_validate_incoming和tcp_sequence

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