TCP連接的狀態
首先介紹一下TCP連接建立與關閉過程中的狀態。TCP連接過程是狀態的轉換,促使狀態發生轉換的因素包括用戶調用、特定數據包以及超時等,具體狀態如下所示:
CLOSED:初始狀態,表示沒有任何連接。
LISTEN:Server端的某個Socket正在監聽來自遠方的TCP端口的連接請求。
SYN_SENT:發送連接請求后等待確認信息。當客戶端Socket進行Connect連接時,會首先發送SYN包,隨即進入SYN_SENT狀態,然后等待Server端發送三次握手中的第2個包。
SYN_RECEIVED:收到一個連接請求后回送確認信息和對等的連接請求,然后等待確認信息。通常是建立TCP連接的三次握手過程中的一個中間狀態,表示Server端的Socket接收到來自Client的SYN包,并作出回應。
ESTABLISHED:表示連接已經建立,可以進行數據傳輸。
FIN_WAIT_1:主動關閉連接的一方等待對方返回ACK包。若Socket在ESTABLISHED狀態下主動關閉連接并向對方發送FIN包(表示己方不再有數據需要發送),則進入FIN_WAIT_1狀態,等待對方返回ACK包,此后還能讀取數據,但不能發送數據。在正常情況下,無論對方處于何種狀態,都應該馬上返回ACK包,所以FIN_WAIT_1狀態一般很難見到。
FIN_WAIT_2:主動關閉連接的一方收到對方返回的ACK包后,等待對方發送FIN包。處于FIN_WAIT_1狀態下的Socket收到了對方返回的ACK包后,便進入FIN_WAIT_2狀態。由于FIN_WAIT_2狀態下的Socket需要等待對方發送的FIN包,所有常常可以看到。若在FIN_WAIT_1狀態下收到對方發送的同時帶有FIN和ACK的包時,則直接進入TIME_WAIT狀態,無須經過FIN_WAIT_2狀態。
TIME_WAIT:主動關閉連接的一方收到對方發送的FIN包后返回ACK包(表示對方也不再有數據需要發送,此后不能再讀取或發送數據),然后等待足夠長的時間(2MSL)以確保對方接收到ACK包(考慮到丟失ACK包的可能和迷路重復數據包的影響),最后回到CLOSED狀態,釋放網絡資源。
CLOSE_WAIT:表示被動關閉連接的一方在等待關閉連接。當收到對方發送的FIN包后(表示對方不再有數據需要發送),相應的返回ACK包,然后進入CLOSE_WAIT狀態。在該狀態下,若己方還有數據未發送,則可以繼續向對方進行發送,但不能再讀取數據,直到數據發送完畢。
LAST_ACK:被動關閉連接的一方在CLOSE_WAIT狀態下完成數據的發送后便可向對方發送FIN包(表示己方不再有數據需要發送),然后等待對方返回ACK包。收到ACK包后便回到CLOSED狀態,釋放網絡資源。
CLOSING:比較罕見的例外狀態。正常情況下,發送FIN包后應該先收到(或同時收到)對方的ACK包,再收到對方的FIN包,而CLOSING狀態表示發送FIN包后并沒有收到對方的ACK包,卻已收到了對方的FIN包。有兩種情況可能導致這種狀態:其一,如果雙方幾乎在同時關閉連接,那么就可能出現雙方同時發送FIN包的情況;其二,如果ACK包丟失而對方的FIN包很快發出,也會出現FIN先于ACK到達。
圖解三次握手
實線代表服務器 的狀態轉換、虛線代表客戶端 的轉態轉換;
文字講解三次握手:
最開始客戶端和服務器,都處于 CLOSED 狀態,二者之間沒有任何聯系;
客戶端向服務器發起連接請求,具體操作是發送一個 SYN 給服務器,客戶端狀態轉而變為 SYN-SENT ,這是第一次握手 ;
由于客戶端向服務器發送請求了,服務器被動的進入 LISTEN 狀態;
如果服務器沒有想要客戶端的請求,則客戶端得不到服務器的回應,請求就會超時,一旦請求超時,客戶端的狀態,就會再次進入 CLOSED ;
如果服務器響應了這個請求,具體操作是:接收到了客戶端發送的 SYN,同時向客戶端發送回應 SYN+ACK,表示我收到你的請求了;然后服務器的狀態,進入 SYN-RECEIVED ; 這是第二次握手 ;
客戶端收到服務器的回應 SYN+ACK,然后再次向服務器發送一個 ACK,表示我收到你的回應了 ;客戶端的狀態進入 ESTABLISHED ;這是第三次握手 ;
服務器收到客戶端的響應 ACK 以后,狀態進入 ESTABLISHED ;
至此,三次握手完成,客戶端與服務器建立了可靠的連接,可以進行數據傳輸了 ;
上面有個特殊的地方:
假如客戶端發送請求報文SYN的時候,服務器也發送了請求報文SYN ,則客戶端進入 SYN-RECEIVED 狀態,角色變為服務器; 一般這種情況是,沒有絕對的客戶端、服務器;通信的兩臺機器,可以在客戶端和服務器之間進行角色的切換 ;
圖解四次揮手
上面
實線代表主動關閉方 的狀態轉換、虛線代表被動關閉方 的轉態轉換;
文字講解四次揮手:
主動關閉方,發送 FIN 給被動關閉方,然后進入 FIN-WAIT-1 狀態 ;
被動關閉方,收到主動關閉方的 FIN,會送一個回應 ACK 給主動發送方,然后進入 CLOSE-WAIT ;
主動方收到被動方回應的ACK,并且也回應一個ACK,然后進入 FIN-WAIT-2 ;
既然主動方要關閉連接,那么被動方也不能死皮賴臉的不關啊,它也就向主動方發送一個請求FIN,然后進入 LAST-ACK ;
被動方收到主動方的回應ACK,進入 CLOSED
主動方收到被動方發送的 FIN 之后,發送回應 ACK(主動方在接收到被動方發送的FIN,將不再接受任何信息,但是可以發送信息),進入 TIME-WAIT
上面是有一方先于對方,發起關閉請求 ,下面說下,雙方同時發起關閉請求的情況;
當雙方同時發起關閉請求的時候,雙方在發送完FIN 以后,都進入 FIN-WAIT-1 狀態;然后如果雙發又同時收到對方的FIN,以及同時收到對方回應的ACK,則直接進入 TIME-WAIT ;
如果雙方同時收到FIN,但是沒有同時收到ACK,則先進入 CLOSING,然后,在雙方都收到 ACK 以后,再進入 TIME-WAIT ;
為什么在進入 TIME-WAIT 的狀態以后,會等待 2MSL 的時間,再進入 CLOSED?
MSL 翻譯為:報文最大生存時間 ;,2MSL則是兩個報文最大生存世時間,等待這個時間的原因:是因為在網絡不好的時候,有時候,需要重新發送報文,因此進入這里的等待下 ;
對Server與Client的影響
在詳細了解TCP連接的狀態和關閉方式后,我們會發現TIME_WAIT狀態是一個坑爹的存在!主動關閉連接的一方在發送最后一個ACK包后,無論對方是否收到都會進入TIME_WAIT狀態,等待2MSL的時間,然后才能釋放網絡資源。MSL就是Maximum Segment Lifetime(數據包的最大生命周期),是一個數據包能在互聯網上生存的最長時間,若超過這個時間則該數據包將會消失在網絡中。操作系統通常會將2MSL設為4分鐘,最低不少于30秒,因而TIME_WAIT狀態一般維持在30秒至4分鐘。這個是TCP/IP協議必不可少的,是TCP/IP設計者設計的,也就是無法解決的。TIME_WAIT狀態的存在主要有兩個原因:
可靠地實現TCP全雙工連接的終止。在關TCP閉連接時,最后的ACK包是由主動關閉方發出的,如果這個ACK包丟失,則被動關閉方將重發FIN包,因此主動方必須維護狀態信息,以允許它重發這個ACK包。如果不維持這個狀態信息,那么主動方將回到CLOSED狀態,并對被動方重發的FIN包響應RST包,而被動關閉方將此包解釋成一個錯誤(在Java中會拋出connection reset的SocketException)。因而,要實現TCP全雙工連接的正常終止,必須能夠處理四次握手協議中任意一個包丟失的情況,主動關閉方必須維持狀態信息進入TIME_WAIT狀態。
確保迷路重復數據包在網絡中消失,防止上一次連接中的包迷路后重新出現,影響新連接。TCP數據包可能由于路由器異常而迷路,在迷路期間,數據包發送方可能因超時而重發這個包,迷路的數據包在路由器恢復后也會被送到目的地,這個迷路的數據包就稱為Lost Duplicate。在關閉一個TCP連接后,如果馬上使用相同的IP地址和端口建立新的TCP連接,那么有可能出現前一個連接的迷路重復數據包在前一個連接關閉后再次出現,影響新建立的連接。為了避免這一情況,TCP協議不允許使用處于TIME_WAIT狀態的連接的IP和端口啟動一個新連接,只有經過2MSL的時間,確保上一次連接中所有的迷路重復數據包都已消失在網絡中,才能安全地建立新連接。
對于Client而言,每個連接都需要占用一個端口,而系統允許的可用端口數不足65000個(這也是在TCP參數優化后才能達到)。因此,如果Client發起過多的連接并主動關閉(假設沒有重用端口或者連接多個Server),就會有大量的連接在關閉后處于TIME_WAIT狀態,等待2MSL的時間后才能釋放網絡資源(包括端口),于是Client會由于缺少可用端口而無法新建連接。
對Server而言(特別是處理高并發短連接的Server),Server端與Client建立的連接是使用同一個端口的,即監聽的端口,每個連接通過一個五元組區分,包括源IP地址、源端口、傳輸層協議號(協議類型)、目的IP地址、目的端口,因而在理論上,Server不受系統端口數的限制。但是,Server對每個端口上的連接數是有限制的,它要使用哈希表記錄端口上的每個連接,并受到文件描述符的最大打開數的限制。所以,如果Server主動關閉連接,同樣會有大量的連接在關閉后處于TIME_WAIT狀態,等待2MSL的時間后才能釋放網絡資源(包括哈希表上的連接記錄和文件描述符),于是Server會由于達到哈希表和文件描述符的限制而無法接受新連接,造成性能的急劇下滑,性能曲線會持續產生嚴重的波動。對于這種情況,有三種應對方式:
試圖讓Client主動關閉連接,由于每個Client的并發量都比較低,因而不會產生性能瓶頸。
優化Server的系統TCP參數,使其網絡資源的最大值、消耗速度和恢復速度達到平衡。
改寫TCP協議,重新實現底層代碼,不過該方式難度很大,而且系統的穩定性和安全性可能受到影響。
Windows系統下的TCP參數優化
通常會采用修改注冊表的方式改進Windows的系統參數。下面將為大家介紹Windows系統下的TCP參數優化方式,適用于Windows 2003、Windows XP、Windows 7以及Server版。對于具體的系統環境與性能需求,優化方式會有所差異,效果也不盡相同,僅是個人的建議。所有的優化操作都通過修改注冊表實現,需要使用regedit命令進入注冊表并創建或修改參數,修改完成后需要重啟系統,以使之生效。以下使用的參數值均為10進制。
1. TCPWindowSize
TCPWindowSize的值表示TCP的窗口大小。TCP Receive Window(TCP數據接收緩沖)定義了發送端在沒有獲得接收端的確認信息的狀態下可以發送的最大字節數。此數值越大,返回的確認信息就越少,相應的在發送端和接收端之間的通信就越好。此數值較小時可以降低發送端在等待接收端返回確認信息時發生超時的可能性,但這將增加網絡流量,降低有效吞吐率。TCP在發送端和接收端之間動態調整一個最大段長度MSS(Maximum Segment Size)的整數倍。MSS在連接開始建立時確定,由于TCP Receive Window被調整為MSS的整數倍,在數據傳輸中完全長度的TCP數據段的比例增加,故而提高了網絡吞吐率。
缺省情況下,TCP將試圖根據MSS來優化窗口大小,起始值為16KB,最大值為64KB。TCPWindowSize的最大值通常為65535字節(64KB),以太網最大段長度為1460字節,低于64KB的1460的最大整數倍為62420字節。
重置(RST)
幾種TCP連接中出現RST的情況
端口未打開
請求超時
提前關閉
在一個已關閉的socket上收到數據
TCP重置是一個好的事情,如果沒有reset,我們將會遇到各種各樣的TCP網絡連接問題。需要注意的是導致reset的原因是多種多樣的,不僅僅是兩端的節點還有可能是應用程序,追查問題時最重要的是查看包的狀態以及其重傳。
一個案例
當我們僅僅打開服務端之后(端口號為5188),我們來看看所處的狀態。
打開服務端:
調用命令查看所有的網絡狀態:netstat
然后,我們通過命令:摘取有關tcp的狀態:netstat -an |grep tcp
緊接著為了刪減出有效的信息,我們只需要tcp協議,5188這個端口,我們可以這樣做:
netstat -an|grep tcp|grep 5188
此刻,可以看到,這里的狀態是處于LISTEN,調用的accept函數還是在阻塞著,等待著返回。
這時,再次打開客戶端,繼續觀察一下狀態:
然后,繼續調用之前的命令:
netstat -an|grep tcp|grep 5188
當客戶端一打開,那么就完成了TCP的建立,這里,我們可以看到有兩個是:ESTABLISHED
其中第二行的42555表示的是客戶端所打開的端口,5188是服務端所打開的端口,客戶端連向了服務器端。
由于我們上面的測試是在同一臺主機上的,所以會出現上面的三種信息
而對于其他的狀態而言,只是因為狀態的轉化時間非常短(三次握手,四次揮手完成的特別快),我們不
去探究具體的狀態,下面
1.查找服務器進程:
ps -ef | grep echoserv
分析其pid號,知道了我們此刻打開的是中間的這個服務端(21858,21849)
所以,此刻,我們殺死這個進程:
kill -9 21858
到啦這里,我們再次查看一下狀態:
至于為什么會產生一個FIN_WAIT2, 而不是TIME_WAIT狀態呢?這是因為:我們程序中是這樣處理的,服務端關閉之后,然后客戶端接收到啦這個分節,并向服務端發送了當前的分節確認,然后自己阻塞在了從鍵盤獲取字符的這個位置,并不能運行到函數read處去,也就是說,read函數壓根就不會返回0,所以客戶端就不會重新向服務端重新發送關閉連接的分節,也就停留在此刻了,同樣的,
服務端接受到啦確認分節,那么自己的狀態就變成了FIN_WAIT_2,這樣就解釋的說明問題了
-
IP
+關注
關注
5文章
1718瀏覽量
149970 -
服務器
+關注
關注
12文章
9308瀏覽量
86071 -
TCP
+關注
關注
8文章
1378瀏覽量
79305
發布評論請先 登錄
相關推薦
評論