理解Time-Wait

What is it?

TIME-WAIT狀態的主要做用在於TCP鏈接的拆除階段。拆除一個TCP鏈接一般須要交換4個segment,以下圖所示:程序員

image

Host1上的應用程序關閉了本身這一端的鏈接,使得TCP向Host 2發送了一個FIN。Host 2對這個FIN進行ACK,並將這個FIN做爲一個EOF傳送給應用程序(假設Host 2上的應用程序有一個掛起的read操做)。通過一段時間後,Host 2上的應用程序close掉本身那一端的鏈接,這樣TCP會向Host 2發送一個FIN,Host 1會以一個ACK應答。服務器

此時,Host 2關閉鏈接,並釋放資源。從Host 2的角度來看,鏈接已經不復存在了。可是,Host 1尚未關閉鏈接,而是會進入TIME-WAIT狀態,並在這個狀態停留2MSL(2 Maximum Segment Lifetime)。網絡

MSL(最大分段壽命)是segment被丟棄以前在網絡中能夠存活的最大時長。socket

等待了2MSL以後,Host 1也將TCP鏈接關閉,並釋放資源。ide

注意blog

關於TIME-WAIT狀態,須要注意如下3點:資源

1. 一般,只有主動關閉TCP鏈接的那一端會進入TIME-WAIT狀態。get

主動關閉表示發送第一條FIN的那一端。另外一端稱爲被動關閉。有一種多是同時關閉,即兩端同時關閉鏈接,並向網絡發送FIN,在這種狀況下,就認爲兩個應用程序都進行了主動關閉,兩端都會進入TIME-WAIT狀態。it

2. RFC將MSL定義爲2分鐘,根據這個定義,鏈接會在TIME-WAIT狀態停留4分鐘。但實際上,各實現均有不一樣,通常TIME-WAIT狀態持續時間在1分鐘到4分鐘之間。io

3. 若是鏈接處於TIME-WAIT狀態時有segment到達,就重啓2MSL的定時器。

爲何須要TIME-WAIT?

使用TIME-WAIT狀態主要有兩個目的:

1. 維護鏈接狀態

It maintains the connection state in case the final ACK, which is sent by the side doing the active close, is lost, causing the other side to resend its FIN.

若是主動關閉鏈接的那端發送的最後一條ACK丟失,那麼另外一端會從新發送FIN(注意,這二者一來一回最長的時間正好是2MSL),若是此時主動關閉方已經關閉了鏈接的話(也就是說沒有TIME-WAIT的話),那麼主動方收到對方重送的FIN後,因爲TCP已經沒有這條鏈接的記錄了,會發送RST(重置)給對端,對端會將此標記爲error,於是沒法正常關閉鏈接。可是,若是主動方處於TIME-WAIT狀態,那麼它就仍然有這條TCP鏈接的記錄,就能夠對對端重送的FIN進行正確的相應。

這也就解釋了爲何鏈接在TIME-WAIT狀態收到一個segment時,須要重啓2MSL的定時器。若是最後一條ACK丟失,對端重傳FIN,處於TIME-WAIT狀態的這一端會再次對FIN進行ACK,而且重啓定時器以防這一條ACK也丟失。

2. 爲了耗盡網絡中全部基於此鏈接的「走失的segment」提供時間。

It provides time for any ‘stray segments’ from the connection to drain from the network.

IP數據報在網絡上傳輸時,不免會發生丟失或延遲,固然,TCP會有一些機制來保證對沒有被即時ACK的segment進行重傳。

若是沒有TIME-WAIT狀態,而且,這些延遲的或者被重傳的segment在鏈接被關閉後到達,TCP只是將這些segment丟棄並以RST迴應,當RST到達另外一端時,因爲這臺Host中也沒有這條TCP鏈接的記錄了,因此RST會被丟棄,在這種狀況下不會產生問題。可是,若是在這兩臺主機上用一樣的端口號創建了一條新鏈接,那麼以前那個TCP鏈接中延遲的或者被重傳的segment的sequence number可能正好落在新鏈接的接收窗口中(receive sliding window),那麼這部分的數據就會被接收,從而對新鏈接形成破壞。

TIME-WAIT狀態確保了在原有鏈接的全部segment從網絡中消失以前,不會再次使用原來用過的socket對(兩個IP地址以及相應的端口號),所以TIME-WAIT狀態在提供TCP可靠性方面發揮了重要的做用。

TIME-WAIT 暗殺

不幸的是,TIME-WAIT狀態能夠被提早終止,這被稱爲TIME-WAIT暗殺。有如下兩種狀況:

1. RFC 793指出,當一條鏈接處於TIME-WAIT狀態並收到一個RST時,應該當即將鏈接關閉。

假設,如今有一條鏈接處於TIME-WAIT狀態,而且以前一個延遲的或者被retransmission的segment到達,而這個segmentTCP沒法接收(例如序列號在接受窗口以外),TCP會以一個ACK相應,說明它所期待的序列號(就是對等實體的FIN以後的序列號)。但對等實體中已經沒有這個TCP鏈接的記錄了,因此會以一個RST來進行ACK。當這個RST回處處於TIME-WAIT狀態的那一端時,會使鏈接當即關閉——TIME-WAIT狀態被暗殺了。

幸運的是,對TCP進行修改,使其忽略TIME-WAIT狀態下的RST便可,某些協議棧進行了這樣的修改。

2. 另外一種TIME-WAIT暗殺的方式是有意爲之的。

即便應用程序是TCP鏈接的主動關閉方,程序員也可使用套接字選項 SO_LINGER 迫使鏈接當即關閉,而不通過TIME-WAIT狀態。有時,會推薦使用這種可疑的方式使得服務器跳出TIME-WAIT狀態,這樣就能夠在服務器重啓或者崩潰後快速重啓。可是,健壯的應用程序永遠都不該該干擾TIME-WAIT狀態,由於這是TCP可靠機制的重要組成部分。

一般,應用程序關閉一條鏈接時,即便TCP發送緩衝區中仍然有數據要發送,close調用也會當即返回,雖然,TCP仍是會嘗試着發送未發送出去的數據,可是應用程序並不知道是否發送成功了。爲了防止這個問題的發生,能夠設置套接字選項SO_LINGER(經過填寫linger結構體,並調用setsockopt來設置套接字的SO_LINGER選項)。

image

若是成員l_onoff爲零,那麼linger選項將被關閉,其行爲與默認行爲相同——close調用當即返回,內核繼續嘗試發送全部未發送的數據。

若是l_onoff非零,其行爲取決於l_linger的值。若是l_linger非零,就將l_linger做爲內核等待掛起的數據被髮送出去並被確認(ACK)而逗留的時間間隔。也就是說,close在全部數據都發送完畢,或者時間間隔到期以前是不會返回的。

若是l_linger時間到了,仍然有未發送的數據,close就返回EWOULDBLOCK,全部未發送的數據均可能丟失。若是數據都發送出去了,close就返回0。

固然,以這種方式使用SO_LINGER,能保證的也只是數據到達了對方的TCP接收緩衝區中,不能保證數據被對面的應用程序讀走。

最後若是l_onoff非零,l_linger爲零,TCP鏈接會被丟棄。也就是說,向對等實體發送一個RST(代表鏈接已經不存在),不通過TIME-WAIT狀態就當即關閉鏈接。這就是所謂的有意的TIME-WAIT暗殺。

相關文章
相關標籤/搜索