TCP使用四種定時器(Timer,也稱為“計時器”):
重傳計時器:Retransmission Timer
堅持計時器:Persistent Timer
保活計時器:Keeplive Timer
時間等待計時器:Time_Wait Timer。

(1)重傳計時器:
很明顯重傳定時器是用來計算TCP封包段的逾時重傳時間的(至于逾時重傳時間的确定,這裡涉及到一大堆的算法,書上有說,我這裡不細談了)。每發送一個封包段就會啟動重傳定時器,如果在定時器時間到後還沒收到對該封包段的确認,就重傳該封包段,并将重傳定時器複位,重新計算;如果在規定時間内收到了對該封包段的确認,則撤銷該封包段的重傳定時器。
重傳定時器:為了控制丢失的封包段或丢棄的封包段,也就是對封包段确認的等待時間。當TCP發送封包段時,就建立這個特定封包段的重傳計時器,可能發生兩種情況:若在計時器逾時之前收到對封包段的确認,則撤銷計時器;若在收到對特定封包段的确認之前計時器逾時,則重傳該封包,并把計時器複位;
重傳時間=2*RTT;
RTT的值應該動态計算。常用的公式是:RTT=previous RTT*i + (1-i)*current RTT。i的值通常取90%,即新的RTT是以前的RTT值的90%加上目前RTT值的10%.
Karn算法:對重傳封包,在計算新的RTT時,不考慮重傳封包的RTT。因為無法推理出:發送端所收到的确認是對上一次封包段的确認還是對重傳封包段的确認。幹脆不計入。
(2)堅持計時器:persistent timer
專門為對付零視窗通知而設立的。
當發送端收到零視窗的确認時,就啟動堅持計時器,當堅持計時器截止期到時,發送端TCP就發送一個特殊的封包段,叫探測封包段,這個封包段隻有一個位元組的資料。探測封包段有序号,但序号永遠不需要确認,甚至在計算對其他部分資料的确認時這個序号也被忽略。探測封包段提醒接收端TCP,确認已丢失,必須重傳。
堅持計時器的截止期設定為重傳時間的值,但若沒有收到從接收端來的響應,則發送另一個探測封包段,并将堅持計時器的值加倍和并複位,發送端繼續發送探測封包段,将堅持計時器的值加倍和複位,知道這個值增大到門檻值為止(通常為60秒)。之後,發送端每隔60s就發送一個封包段,直到視窗重新打開為止;
(3)保活計時器:keeplive timer
每當伺服器收到客戶的資訊,就将keeplive timer複位,逾時通常設定2小時,若伺服器超過2小時還沒有收到來自客戶的資訊,就發送探測封包段,若發送了10個探測封包段(沒75秒發送一個)還沒收到響應,則終止連接配接。
伺服器應用程式用來探知客戶主機是否崩潰并啟動,或者崩潰關機等場景。
具體來說客戶主機必定處于以下4中狀态之一:
- 正常運作。TCP響應正常,服務端知道用戶端工作正常,伺服器在兩小時後将保活定時器複位,如果這兩小時之間有應用程式通過這個連接配接通信,保活定時器在交換資料後的未來兩小時再複位;此時的伺服器應用程式不需要感覺保活定時器
- 客戶主機崩潰,并且關閉或者正在重新開機。伺服器總共發送10個探查,每個間隔75秒,如果沒有任何響應,認為客戶主機關閉并終止連接配接;
- 客戶主機崩潰但已經重新開機。伺服器收到保活探查的響應,但響應回會是個複位,使得伺服器終止連接配接;
- 客戶主機正常,但是服務不可達。與2類似,隻能得到沒有探查響應
缺點在于:1短暫差錯可能使得一個好的連接配接被釋放;2保活浪費不必要的帶寬;
(4)時間等待計時器:Time_Wait Timer,也叫 2MSL定時器
在連接配接終止期使用,當TCP關閉連接配接時,并不認為這個連接配接就真正關閉了,在時間等待期間,連接配接還處于一種中間過度狀态。這樣就可以時重複的fin封包段在到達終點後被丢棄,這個計時器的值通常設定為一格封包段壽命期望值的兩倍。
2MSL定時器測量一個連接配接處于TIME—WAIT黃台的時間,通常為2MSL(封包段壽命的兩倍)。2MSL定時器的設定主要是為了確定發送的最後一個ACK封包段能夠到達對方,并防止之前與本連接配接有關的由于延遲等原因而導緻已失效的封包被誤判為有效。
參考文獻:《TCP/IP協定族》 Forouzan著,謝希仁譯;
如何處理TCP連接配接中打開視窗的ACK丢失的情況?
關閉視窗的場景:接收方通告發送方接收的資料視窗為0,這個時候發送方不再發送資料;
打開視窗ACK丢失的危害:當接收方通告了一個視窗為非0的ACK,此ACK由于某種原因丢失,此時發送方在永遠的等視窗打開的通知,接收方則永遠的在等新資料的到來,這樣有可能因為等待而造成連接配接關閉。
解決政策:使用堅持定時器,周期性的向接收方查詢,以便發現視窗的變化
什麼是糊塗視窗綜合症?
對于伺服器來講,如果處理的速度過于緩慢,他會将通告視窗的值設定的越來越小,甚至是小于封包頭,這種情況下,通信的效率極其低下,這種情況稱作糊塗視窗綜合症。
如何避免糊塗視窗綜合症?
- 接收方:當視窗增加一個封包段大小(MSS)或者可以增加接收方緩存空間一半時,才通告視窗大小
- 發送方:當可以發送一個滿長度的封包、發送至少是接收方通告視窗大小的一半的封包或者是可以發送任何資料并且不希望接收ACK(這種情況,資料都已經确認了)
Nagle算法中,當封包太小的時候就不發送,這裡的小,可以看出發送方發送的封包小于封包段大小
TCP的4個定時器
對每個連接配接,TCP管理4個不同的定時器。
- 重傳定時器使用于當希望收到另一端的确認。
TCP在發送一份資料後,啟動重傳定時器,在經過若幹時間後如果沒有收到ACK,則重傳該資料,這個時間間隔通常如下:1s, 3s, 6s, 12s, 24, 48s, 和多個64s。這個指數關系被稱為指數退避。TCP會一直嘗試重傳該資料,在9分鐘以後放棄。
- 堅持定時器使視窗大小資訊保持不斷流動,即使另一端關閉了其接收視窗。
假定有如下情況:接收方接收到一定量的資料,發現本地緩存滿了,于是發送一個視窗大小為0的ACK給發送方,發送方在接收到該ACK後停止發送。一段時間後,接收方有空餘的視窗,于是發送一個視窗大小非0的ACK以更新視窗,不幸的是該ACK丢失了,于是整個連接配接被停止。堅持定時器正是為了解決這個問題,它在接收到一個視窗大小為0的ACK以後,啟動一個定時器,定時去查詢是否視窗已經更新(同樣采用指針退避)。
- 保活定時器可檢測到一個空閑連接配接的另一端何時崩潰或重新開機。
有如下情況,連接配接建立好以後,發送方不發送任何資料,這樣一直下去(有可能是發送方系統崩潰,或者網絡被意外切斷),這就導緻服務端一直占用該連接配接,浪費資源。保活定時器通常每兩小時發送一個探測請求以檢視對方是否存活。
- 2MSL定時器測量一個連接配接處于TIME_WAIT狀态的時間。
2MSL時間是為了讓TCP有時間發送最後一個ACK,防止該ACK丢失。
堅持定時器
一個例子
為了觀察到實際中的堅持定時器,我們啟動一個接收程序。它監聽來自客戶的連接配接請求,接受該連接配接請求,然後在從網上讀取資料前休眠很長一段時間。
sock程式可以通過指定一個暫停選項 - P使伺服器在接受連接配接和進行第一次讀動作之間進入休眠。我們以這種方式調用伺服器:
svr4 % sock -i -s -P100000 5555
該指令在從網絡上讀資料之前休眠 100 000秒(27.8小時)。客戶運作在主機bsdi上,并向伺服器的5555端口執行1024位元組的寫操作。下圖給出了tcpdump的輸出結果(我們已經在結果中去掉了連接配接的建立過程)。
封包段1 ~ 1 3顯示的是從客戶到伺服器的正常的資料傳輸過程,有9216個位元組的資料填充了視窗。伺服器通告視窗大小為4096位元組,且預設的插口緩存大小為4096位元組。但實際上它一共接收了9216位元組的資料,這是在S V R 4中TCP代碼和流子系統(stream subsystem)之間某種形式互動的結果。
在封包段1 3中,伺服器确認了前面 4個資料封包段,然後通告視窗為 0,進而使客戶停止發送任何其他的資料。這就引起客戶設定其堅持定時器。如果在該定時器時間到時客戶還沒有接收到一個視窗更新,它就探查這個空的視窗以決定視窗更新是否丢失。由于伺服器程序處于休眠狀态,是以TCP緩存9216位元組的資料并等待應用程序讀取。
請注意客戶發出的視窗探查之間的時間間隔。在收到一個大小為 0的視窗通告後的第1個(封包段1 4)間隔為4.949秒,下一個(封包段16)間隔是4.996秒,随後的間隔分别約為6,12,24,48和60秒。
為什麼這些間隔總是比5、6、12、24、48和60小一個零點幾秒呢?因為這些探查被TCP的500 ms定時器逾時例程所觸發。當定時器時間到時,就發送視窗探查,并大約在4 ms之後收到一個應答。
接收到應答使得定時器被重新啟動,但到下一個時鐘滴答之間的時間則約為500減4ms。計算堅持定時器時使用了普通的 TCP指數退避。對一個典型的區域網路連接配接,首次逾時時間算出來是1 . 5秒,第2次的逾時值增加一倍,為 3秒,再下次乘以4為6秒,之後再乘以8為1 2秒等。但是堅持定時器總是在5 ~ 6 0秒之間,這與我們在上圖中觀察到的現象一緻。
視窗探查包含一個位元組的資料(序号為 9 2 1 7)。TCP總是允許在關閉連接配接前發送一個位元組的資料。請注意,盡管如此,所傳回的視窗為0的ACK并不是确認該位元組(它們确認了包括9216在内的所有資料),是以這個位元組被持續重傳。
堅持狀态與重傳逾時之間一個不同的特點就是 TCP從不放棄發送視窗探查。這些探查每隔60秒發送一次,這個過程将持續到或者視窗被打開,或者應用程序使用的連接配接被終止。
總結
産生原因:
首先,接收端發送一個視窗大小為0的ACK封包;發送端收到該信号後,等待接收方發送視窗大小大于0的ACK.
然後,接收端發送視窗大小大于0的封包,但是該封包丢失,因為TCP并不對ACK封包進行确認,這就造成了
接收方等待接收資料,而發送方仍然等待視窗大于0的ACK,産生死鎖。