Tcp的四次揮手狀態機詳解

[摘要:斷開毗鄰實在從我的角度看沒有辨別客戶端戰辦事器端,任何一圓皆能夠挪用close(or closesocket)之類 的函數最先自動停止一個毗鄰。那裏先臨時道一般環境。當挪用close函數斷開一個] 

斷開連接其實從我的角度看不區分客戶端和服務器端,任何一方都可以調用close(or closesocket)之類
的函數開始主動終止一個連接。這裏先暫時說正常情況。當調用close函數斷開一個連接時,主動斷開的
一方發送FIN(finish報文給對方。有了之前的經驗,我想你應該明白我說的FIN報文時什麼東西。也就是
一個設置了FIN標誌位的報文段。FIN報文也可能附加用戶數據,如果這一方還有數據要發送時,將數據附
加到這個FIN報文時完全正常的。之後你會看到,這種附加報文還會有很多,例如ACK報文。我們所要把握
的原則是,TCP肯定會力所能及地達到最大效率,所以你能夠想到的優化方法,我想TCP都會想到。

當被動關閉的一方收到FIN報文時,它會發送ACK確認報文(對於ACK這個東西你應該很熟悉了)。這裏有個
東西要注意,因爲TCP是雙工的,也就是說,你可以想象一對TCP連接上有兩條數據通路。當發送FIN報文
時,意思是說,發送FIN的一端就不能發送數據,也就是關閉了其中一條數據通路。被動關閉的一端發送
了ACK後,應用層通常就會檢測到這個連接即將斷開,然後被動斷開的應用層調用close關閉連接。

我可以告訴你,一旦當你調用close(or closesocket),這一端就會發送FIN報文。也就是說,現在被動
關閉的一端也發送FIN給主動關閉端。有時候,被動關閉端會將ACK和FIN兩個報文合在一起發送。主動
關閉端收到FIN後也發送ACK,然後整個連接關閉(事實上還沒完全關閉,只是關閉需要交換的報文發送
完畢),四次握手完成。如你所見,因爲被動關閉端可能會將ACK和FIN合到一起發送,所以這也算不上
嚴格的四次握手---四個報文段。

在前面的文章中,我一直沒提TCP的狀態轉換。在這裏我還是在猶豫是不是該將那張四處通用的圖拿出來,
不過,這裏我只給出斷開連接時的狀態轉換圖,摘自<The TCP/IP Guide>:


TCP 連接斷開過程-四次握手 - 桂訓龍 - Hello Java

\

給出一個正常關閉時的windump信息:

\14:00:38.819856 IP cd-zhangmin.1748 > 220.181.37.55.80: F 1:1(0) ack 1 win 65535
\14:00:38.863989 IP 220.181.37.55.80 > cd-zhangmin.1748: F 1:1(0) ack 2 win 2920
\14:00:38.864412 IP cd-zhangmin.1748 > 220.181.37.55.80: . ack 2 win 65535

 

補充細節:

關於以上的四次握手,我補充下細節:
1. 默認情況下(不改變socket選項),當你調用close( or closesocket,以下說close不再重複)時,如果
發送緩衝中還有數據,TCP會繼續把數據發送完。


2. 發送了FIN只是表示這端不能繼續發送數據(應用層不能再調用send發送),但是還可以接收數據。


3. 應用層如何知道對端關閉?通常,在最簡單的阻塞模型中,當你調用recv時,如果返回0,則表示對端
關閉。在這個時候通常的做法就是也調用close,那麼TCP層就發送FIN,繼續完成四次握手。如果你不調用
close,那麼對端就會處於FIN_WAIT_2狀態,而本端則會處於CLOSE_WAIT狀態。這個可以寫代碼試試。


4. 在很多時候,TCP連接的斷開都會由TCP層自動進行,例如你CTRL+C終止你的程序,TCP連接依然會正常關
閉,你可以寫代碼試試。

 

 

 

特別的TIME_WAIT狀態:

從以上TCP連接關閉的狀態轉換圖可以看出,主動關閉的一方在發送完對對方FIN報文的確認(ACK)報文後,
會進入TIME_WAIT狀態。TIME_WAIT狀態也稱爲2MSL狀態。

什麼是2MSL?MSL即Maximum Segment Lifetime,也就是報文最大生存時間,引用<TCP/IP詳解>中的話:“
它(MSL)是任何報文段被丟棄前在網絡內的最長時間。”那麼,2MSL也就是這個時間的2倍。其實我覺得沒
必要把這個MSL的確切含義搞明白,你所需要明白的是,當TCP連接完成四個報文段的交換時,主動關閉的
一方將繼續等待一定時間(2-4分鐘),即使兩端的應用程序結束。你可以寫代碼試試,然後用netstat查看下。

爲什麼需要2MSL?根據<TCP/IP詳解>和<The TCP/IP Guide>中的說法,有兩個原因:
其一,保證發送的ACK會成功發送到對方,如何保證?我覺得可能是通過超時計時器發送。這個就很難用
代碼演示了。
其二,報文可能會被混淆,意思是說,其他時候的連接可能會被當作本次的連接。直接引用<The TCP/IP Guide>
的說法:The second is to provide a “buffering period” between the end of this connection 
and any subsequent ones. If not for this period, it is possible that packets from different 
connections could be mixed, creating confusion.

TIME_WAIT狀態所帶來的影響:

當某個連接的一端處於TIME_WAIT狀態時,該連接將不能再被使用。事實上,對於我們比較有現實意義的
是,這個端口將不能再被使用。某個端口處於TIME_WAIT狀態(其實應該是這個連接)時,這意味着這個TCP
連接並沒有斷開(完全斷開),那麼,如果你bind這個端口,就會失敗。

對於服務器而言,如果服務器突然crash掉了,那麼它將無法再2MSL內重新啓動,因爲bind會失敗。解決這
個問題的一個方法就是設置socket的SO_REUSEADDR選項。這個選項意味着你可以重用一個地址。

對於TIME_WAIT的插曲:

當建立一個TCP連接時,服務器端會繼續用原有端口監聽,同時用這個端口與客戶端通信。而客戶端默認情況
下會使用一個隨機端口與服務器端的監聽端口通信。有時候,爲了服務器端的安全性,我們需要對客戶端進行
驗證,即限定某個IP某個特定端口的客戶端。客戶端可以使用bind來使用特定的端口。

對於服務器端,當設置了SO_REUSEADDR選項時,它可以在2MSL內啓動並listen成功。但是對於客戶端,當使
用bind並設置SO_REUSEADDR時,如果在2MSL內啓動,雖然bind會成功,但是在windows平臺上connect會失敗。
而在linux上則不存在這個問題。(我的實驗平臺:winxp, ubuntu7.10)

要解決windows平臺的這個問題,可以設置SO_LINGER選項。SO_LINGER選項決定調用close時,TCP的行爲。
SO_LINGER涉及到linger結構體,如果設置結構體中l_onoff爲非0,l_linger爲0,那麼調用close時TCP連接
會立刻斷開,TCP不會將發送緩衝中未發送的數據發送,而是立即發送一個RST報文給對方,這個時候TCP連
接就不會進入TIME_WAIT狀態。

如你所見,這樣做雖然解決了問題,但是並不安全。通過以上方式設置SO_LINGER狀態,等同於設置SO_DONTLINGER
狀態。

斷開連接時的意外:
這個算不上斷開連接時的意外,當TCP連接發生一些物理上的意外情況時,例如網線斷開,linux上的TCP實現
會依然認爲該連接有效,而windows則會在一定時間後返回錯誤信息。

這似乎可以通過設置SO_KEEPALIVE選項來解決,不過不知道這個選項是否對於所有平臺都有效。


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