1、簡介linux
咱們以前介紹過,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通常是針對有效數據來講的)。併發
2、wireshark示例socket
一、綜合示例
tcp
咱們設置tcp_retries2=6,tcp_invalid_ratelimit=1200,經過socket選項SO_RCVBUF設置server端和client端的window size以下圖所示,經過這個示例咱們還會進一步說明一下以前介紹過的延遲ACK的處理。ui
No1-No3:首先client和server端經過三次握手創建TCP鏈接,其中server端的window size爲3000bytesspa
接着client端執行一次write操做,一次write寫入5000bytes的數據。3d
No4:client端內核在從用戶空間讀取數據前會先獲取當前的發送MSS,能夠從圖中看到,server端的接收窗口大小爲3000bytes,可是MSS爲65495bytes,顯然不能按照這個MSS來發送,當出現這種狀況的時候,linux的發送端(即本例中client端)會取對端最大接收窗口的一半1500bytes爲發送mss。選定MSS後,接着linux內核會從client端嘗試以1500bytes爲單位來複制應用程序的數據(共5000bytes)而後發送出去,No4即對應client發出的第一個數據包。orm
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報文的時候發現,前面已經連續發送了No八、No十、No十一、No1三、No1五、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確認包。
補充說明:
一、MSS相對與發送窗口折半的限制處理,請參考tcp_bound_to_half_wnd
二、persist timer的定時器的初始啓動__tcp_push_pending_frames,隨後超時處理tcp_probe_timer
三、linux對於示例中window probe消息的處理以及與參數tcp_invalid_ratelimit的關係,參考tcp_validate_incoming和tcp_sequence