TCP連接的TIME_WAIT和CLOSE_WAIT 狀態解說-運維筆記

相信很多運維工程師遇到過這樣一個情形: 用戶反饋網站訪問巨慢, 網絡延遲等問題, 然後就迫切地登錄服務器,終端輸入命令"netstat -anp | grep TIME_WAIT | wc -l " 查看一下, 接着發現有幾百幾千甚至幾萬個TIME_WAIT 連接數. 頓時慌了~

通過 "netstat  -anp | grep TIME_WAIT | wc -l"  命令查看數量,發現TIME_WAIT的連接數量很多! 可能是因爲服務器主動關閉連接導致TIME_WAIT產生了很多.
發現系統存在大量TIME_WAIT狀態的連接, 可以通過調整系統內核參數來解決:
  
打開 sysctl.conf 文件,修改以下幾個參數:
[root@web01 ~]# vim  /etc/sysctl.conf
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_timestamps = 1

net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_fin_timeout = 30
  
[root@web01 ~]# sysctl -p
  
接着被告知: 開啓tw_recylce和tw_reuse功能, 一定需要timestamps的支持,而且這些配置一般不建議開啓,但是對解決TIME_WAIT很多的問題,有很好的用處。
果然, 經過如上配置後, 過了幾分鐘,再查看TIME_WAIT的數量快速下降了不少,並且後面也沒發現哪個用戶說有問題了. 做到這裏, 相信大多數運維人員想當然地以
爲問題已經解決了,但是,要徹底理解並解決這個問題,可能就沒這麼簡單,或者說,要想徹底搞清楚並解決這個問題, 還是有很長的路要走滴!
  
相關查看命令:
[root@web01 ~]# netstat -n | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"\t",state[key]}'
會得到類似下面的結果,具體數字會有所不同:
LAST_ACK 1
SYN_RECV 14
ESTABLISHED 79
FIN_WAIT1 28
FIN_WAIT2 3
CLOSING 5
TIME_WAIT 1669
  
[root@web01 ~]# sysctl -a | grep time | grep wait
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 120
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120
  
執行命令"netstat -na"查看到的相關TCP狀態解釋:
LISTEN:       偵聽來自遠方的TCP端口的連接請求;
SYN-SENT:     在發送連接請求後等待匹配的連接請求;
SYN-RECEIVED: 在收到和發送一個連接請求後等待對方對連接請求的確認;
ESTABLISHED:  代表一個打開的連接;
FIN-WAIT-1:   等待遠程TCP連接中斷請求, 或先前的連接中斷請求的確認;
FIN-WAIT-2:   從遠程TCP等待連接中斷請求;
CLOSE-WAIT:   等待從本地用戶發來的連接中斷請求;
CLOSING:      等待遠程TCP對連接中斷的確認;
LAST-ACK:     等待原來的發向遠程TCP的連接中斷請求的確認;
TIME-WAIT:    等待足夠的時間以確保遠程TCP接收到連接中斷請求的確認;
CLOSE:        沒有任何連接狀態;

下面簡單解釋下什麼是TIME-WAIT和CLOSE-WAIT ?

通常來說要想解決問題,就要先理解問題。有時遇到問題,上網百度個解決方案,臨時修復了問題,就以爲問題已經不在了, 其實問題不是真的不存在了,而是可能隱藏在更深的地方,只是我們沒有發現,或者以現有自己的的知識水平無法發現而已。總所周知,由於socket是全雙工的工作模式,一個socket的關閉,是需要四次握手來完成的: 1) 主動關閉連接的一方,調用close();協議層發送FIN包 ; 2) 被動關閉的一方收到FIN包後,協議層回覆ACK;然後被動關閉的一方,進入CLOSE_WAIT狀態,主動關閉的一方等待對方關閉,則進入FIN_WAIT_2狀態;此時,主動關閉的一方等待被動關閉一方的應用程序,調用close操作 ; 3) 被動關閉的一方在完成所有數據發送後,調用close()操作;此時,協議層發送FIN包給主動關閉的一方,等待對方的ACK,被動關閉的一方進入LAST_ACK狀態; 4) 主動關閉的一方收到FIN包,協議層回覆ACK;此時,主動關閉連接的一方,進入TIME_WAIT狀態;而被動關閉的一方,進入CLOSED狀態 ; 5) 等待2MSL時間,主動關閉的一方,結束TIME_WAIT,進入CLOSED狀態 ;

通過上面的一次socket關閉操作,可以得出以下幾點: 1) 主動關閉連接的一方 – 也就是主動調用socket的close操作的一方,最終會進入TIME_WAIT狀態 ; 2) 被動關閉連接的一方,有一箇中間狀態,即CLOSE_WAIT,因爲協議層在等待上層的應用程序,主動調用close操作後才主動關閉這條連接 ; 3) TIME_WAIT會默認等待2MSL時間後,才最終進入CLOSED狀態; 4) 在一個連接沒有進入CLOSED狀態之前,這個連接是不能被重用的!

所以說這裏憑直覺看,TIME_WAIT並不可怕,CLOSE_WAIT纔可怕,因爲CLOSE_WAIT很多,表示說要麼是你的應用程序寫的有問題,沒有合適的關閉socket;要麼是說,你的服務器CPU處理不過來(CPU太忙)或者你的應用程序一直睡眠到其它地方(鎖,或者文件I/O等等),你的應用程序獲得不到合適的調度時間,造成你的程序沒法真正的執行close操作。

那麼這裏又出現兩個問題: 1) 上面提到的連接重用,那連接到底是個什麼概念? 2) 協議層爲什麼要設計一個TIME_WAIT狀態?這個狀態爲什麼默認等待2MSL時間纔會進入CLOSED

先解釋清楚這兩個問題後, 接着再來看開頭提到的/etc/sysctl.conf文件中那幾個網絡配置參數究竟有什麼用,以及TIME_WAIT的後遺症問題。

Socket連接到底是個什麼概念? socket 其實就是一個五元組,包括:源IP, 源端口, 目的IP, 目的端口, 類型(TCP or UDP) . 這個五元組,即標識了一條可用的連接。 需要注意是是,經常有很多人把一個socket定義成四元組,也就是源IP:源端口+目的IP:目的端口,這個定義是不正確的。

比如說,如果本地出口IP是110.122.144.166,那麼你的瀏覽器在連接某一個Web服務器,例如百度的時候,這條socket連接的四元組可能就是:[110.122.144.166:45678, tcp, 110.88.92.104:80] , 源IP爲你的出口IP地址 110.122.144.166,源端口爲隨機端口 45678,目的IP爲百度的某一個負載均衡服務器IP 110.88.92.104,端口爲HTTP標準的80端口。

如果這個時候,你再開一個瀏覽器,訪問百度,將會產生一條新的連接:[110.122.144.166:43678, tcp, 110.88.92.104:80] , 這條新的連接的源端口爲一個新的隨機端口 43678。如此來看,如果你的本機需要壓測百度,那麼你最多可以創建多少個連接呢?

TIME_WAIT有什麼用? 如果來做個類比的話,TIME_WAIT的出現,對應的是你的程序裏的異常處理,它的出現,就是爲了解決網絡的丟包和網絡不穩定所帶來的其他問題:

1) 防止前一個連接【五元組,這裏繼續以 110.122.144.166:45678, tcp, 110.88.92.104:80 爲例】上延遲的數據包或者丟失重傳的數據包,被後面複用的連接【前一個連接關閉後,此時你再次訪問百度,新的連接可能還是由110.122.144.166:45678, tcp, 110.88.92.104:80 這個五元組來表示,也就是源端口湊巧還是45678】錯誤的接收(異常:數據丟了,或者傳輸太慢了),參見下圖:

-  SEQ=3的數據包丟失,重傳第一次,沒有得到ACK確認 -  如果沒有TIME_WAIT,或者TIME_WAIT時間非常端,那麼關閉的連接【110.122.144.166:45678, tcp, 110.88.92.104:80 的狀態變爲了CLOSED,源端口可被再次利用】,馬上被重用【對110.88.92.104:80新建的連接,複用了之前的隨機端口45678】,並連續發送SEQ=1,2 的數據包;  -  此時,前面的連接上的SEQ=3的數據包再次重傳,同時,seq的序號剛好也是3(這個很重要,不然,SEQ的序號對不上,就會RST掉),此時,前面一個連接上的數據被後面的一個連接錯誤的接收; 

2) 確保連接方能在時間範圍內,關閉自己的連接。其實,也是因爲丟包造成的,參見下圖:

-  主動關閉方關閉了連接,發送了FIN; -  被動關閉方回覆ACK同時也執行關閉動作,發送FIN包;此時,被動關閉的一方進入LAST_ACK狀態;  -  主動關閉的一方回去了ACK,主動關閉一方進入TIME_WAIT狀態; -  但是最後的ACK丟失,被動關閉的一方還繼續停留在LAST_ACK狀態;  -  此時,如果沒有TIME_WAIT的存在,或者說,停留在TIME_WAIT上的時間很短,則主動關閉的一方很快就進入了CLOSED狀態,也即是說,如果此時新建一個連接,源隨機端口如果被複用,在connect發送SYN包後,由於被動方仍認爲這條連接【五元組】還在等待ACK,但是卻收到了SYN,則被動方會回覆RST;  -  造成主動創建連接的一方,由於收到了RST,則連接無法成功; 

所以,這裏看到了,TIME_WAIT的存在是很重要的,如果強制忽略TIME_WAIT,還是有很高的機率,造成數據粗亂,或者短暫性的連接失敗。那麼,爲什麼說TIME_WAIT狀態會是持續2MSL(2倍的max segment lifetime)呢?這個時間可以通過修改內核參數調整嗎?第一,這個2MSL,是RFC 793裏定義的,參見RFC的截圖標紅的部分:

這個定義,更多的是一種保障(IP數據包裏的TTL,即數據最多存活的跳數,真正反應的纔是數據在網絡上的存活時間),確保最後丟失了ACK,被動關閉的一方再次重發FIN並等待回覆的ACK,一來一去兩個來回。內核裏,寫死了這個MSL的時間爲:30秒(有讀者提醒,RFC裏建議的MSL其實是2分鐘,但是很多實現都是30秒),所以TIME_WAIT的即爲1分鐘.  所以,再次回想一下前面的問題,如果一條連接,即使在四次握手關閉了,由於TIME_WAIT的存在,這個連接,在1分鐘之內,也無法再次被複用,那麼,如果你用一臺機器做壓測的客戶端,你一分鐘能發送多少併發連接請求?如果這臺是一個負載均衡服務器,一臺負載均衡服務器,一分鐘可以有多少個連接同時訪問後端的服務器呢?

TIME_WAIT很多,可怕嗎? 如果你通過 "ss -tan state time-wait | wc -l" 發現,系統中有很多TIME_WAIT,看到時相信很多人都會緊張。多少算多呢?幾百幾千?如果是這個量級,其實真的沒必要緊張。因爲: 這個量級,因爲TIME_WAIT所佔用的內存很少很少;因爲記錄和尋找可用的local port所消耗的CPU也基本可以忽略。會佔用內存嗎?當然!任何你可以看到的數據,內核裏都需要有相關的數據結構來保存這個數據啊。一條Socket處於TIME_WAIT狀態,它也是一條“存在“的socket,內核裏也需要有保持它的數據:

1) 內核裏有保存所有連接的一個hash table,這個hash table裏面既包含TIME_WAIT狀態的連接,也包含其他狀態的連接。主要用於有新的數據到來的時候,從這個hash table裏快速找到這條連接。不同的內核對這個hash table的大小設置不同,你可以通過dmesg命令去找到你的內核設置的大小:

[root@web01 ~]# dmesg |grep --color "TCP established hash table" TCP established hash table entries: 524288 (order: 11, 8388608 bytes)

2) 還有一個hash table用來保存所有的bound ports,主要用於可以快速的找到一個可用的端口或者隨機端口: [root@web01 ~]# dmesg |grep --color "TCP bind hash table" TCP bind hash table entries: 65536 (order: 8, 1048576 bytes)

由於內核需要保存這些數據,必然,會佔用一定的內存。

那麼會消耗CPU嗎?當然!每次找到一個隨機端口,還是需要遍歷一遍bound ports的吧,這必然需要一些CPU時間。TIME_WAIT很多,既佔內存又消耗CPU,這也是爲什麼很多人,看到TIME_WAIT很多,就蠢蠢欲動的想去幹掉他們。其實,如果你再進一步去研究,1萬條TIME_WAIT的連接,也就多消耗1M左右的內存,對現代的很多服務器,已經不算什麼了。至於CPU,能減少它當然更好,但是不至於因爲1萬多個hash item就擔憂。如果要真的想去調優,還是需要搞清楚調優方案以及調優參數背後的意義!

TIME_WAIT調優,則必須理解的幾個調優參數:

net.ipv4.tcp_timestamps RFC 1323 在 TCP Reliability一節裏,引入了timestamp的TCP option,兩個4字節的時間戳字段,其中第一個4字節字段用來保存發送該數據包的時間,第二個4字節字段用來保存最近一次接收對方發送到數據的時間。有了這兩個時間字段,也就有了後續優化的餘地。tcp_tw_reuse 和 tcp_tw_recycle就依賴這些時間字段。

net.ipv4.tcp_tw_reuse 從字面意思來看,這個參數是reuse TIME_WAIT狀態的連接。時刻記住一條socket連接,就是那個五元組,出現TIME_WAIT狀態的連接,一定出現在主動關閉連接的一方。所以,當主動關閉連接的一方,再次向對方發起連接請求的時候(例如,客戶端關閉連接,客戶端再次連接服務端,此時可以複用了;負載均衡服務器,主動關閉後端的連接,當有新的HTTP請求,負載均衡服務器再次連接後端服務器,此時也可以複用),可以複用TIME_WAIT狀態的連接。

通過字面解釋以及例子說明,可以看到,tcp_tw_reuse應用的場景:某一方,需要不斷的通過“短連接“連接其他服務器,總是自己先關閉連接(TIME_WAIT在自己這方),關閉後又不斷的重新連接對方。

那麼,當連接被複用了之後,延遲或者重發的數據包到達,新的連接怎麼判斷,到達的數據是屬於複用後的連接,還是複用前的連接呢?那就需要依賴前面提到的兩個時間字段了。複用連接後,這條連接的時間被更新爲當前的時間,當延遲的數據達到,延遲數據的時間是小於新連接的時間,所以,內核可以通過時間判斷出,延遲的數據可以安全的丟棄掉了。

這個配置,依賴於連接雙方,同時對timestamps的支持。同時,這個配置,僅僅影響outbound連接,即做爲客戶端的角色,連接服務端[connect(dest_ip, dest_port)]時複用TIME_WAIT的socket。

net.ipv4.tcp_tw_recycle 從字面意思來看,這個參數是銷燬掉 TIME_WAIT。當開啓了這個配置後,內核會快速的回收處於TIME_WAIT狀態的socket連接。多快?不再是2MSL,而是一個RTO(retransmission timeout,數據包重傳的timeout時間)的時間,這個時間根據RTT動態計算出來,但是遠小於2MSL。

有了這個配置,還是需要保障丟失重傳或者延遲的數據包,不會被新的連接(注意,這裏不再是複用了,而是之前處於TIME_WAIT狀態的連接已經被destroy掉了,新的連接,剛好是和某一個被destroy掉的連接使用了相同的五元組而已)所錯誤的接收。在啓用該配置,當一個socket連接進入TIME_WAIT狀態後,內核裏會記錄包括該socket連接對應的五元組中的對方IP等在內的一些統計數據,當然也包括從該對方IP所接收到的最近的一次數據包時間。當有新的數據包到達,只要時間晚於內核記錄的這個時間,數據包都會被統統的丟掉。

這個配置,依賴於連接雙方對timestamps的支持。同時,這個配置,主要影響到了inbound的連接(對outbound的連接也有影響,但是不是複用),即做爲服務端角色,客戶端連進來,服務端主動關閉了連接,TIME_WAIT狀態的socket處於服務端,服務端快速的回收該狀態的連接。

由此,如果客戶端處於NAT的網絡(多個客戶端,同一個IP出口的網絡環境),如果配置了tw_recycle,就可能在一個RTO的時間內,只能有一個客戶端和自己連接成功(不同的客戶端發包的時間不一致,造成服務端直接把數據包丟棄掉)。

下面通過案例和圖示,來加深下理解:

-  客戶端IP地址爲:180.172.35.150,我們可以認爲是瀏覽器;  -  負載均衡有兩個IP,外網IP地址爲 115.29.253.156,內網地址爲10.162.74.10;外網地址監聽80端口;  -  負載均衡背後有兩臺Web服務器,一臺IP地址爲 10.162.74.43,監聽80端口;另一臺爲 10.162.74.44,監聽 80 端口;  -  Web服務器會連接數據服務器,IP地址爲 10.162.74.45,監聽 3306 端口; 

這種簡單的架構下,我們來看看,在不同的情況下,上面談論的tw_reuse/tw_recycle對網絡連接的影響。

先做個假定: -  客戶端通過HTTP/1.1連接負載均衡,也就是說,HTTP協議投Connection爲keep-alive,所以假定,客戶端對負載均衡服務器的socket連接,客戶端會斷開連接,所以TIME_WAIT出現在客戶端;  -  Web服務器和MySQL服務器的連接,我們假定,Web服務器上的程序在連接結束的時候,調用close操作關閉socket資源連接,所以,TIME_WAIT出現在 Web 服務器端。

那麼,在這種假定下: -  Web服務器上,肯定可以配置開啓的配置:tcp_tw_reuse;如果Web服務器有很多連向DB服務器的連接,可以保證socket連接的複用。 -  那麼,負載均衡服務器和Web服務器,誰先關閉連接,則決定了我們怎麼配置tcp_tw_reuse/tcp_tw_recycle了。

方案一:負載均衡服務器 首先關閉連接, 在這種情況下,因爲負載均衡服務器對Web服務器的連接,TIME_WAIT大都出現在負載均衡服務器上,所以:

在負載均衡服務器上的配置: net.ipv4.tcp_tw_reuse = 1            //儘量複用連接 net.ipv4.tcp_tw_recycle = 0         //不能保證客戶端不在NAT的網絡啊

在Web服務器上的配置爲: net.ipv4.tcp_tw_reuse = 1         //這個配置主要影響的是Web服務器到DB服務器的連接複用 net.ipv4.tcp_tw_recycle:  設置成1和0都沒有任何意義。想一想,在負載均衡和它的連接中,它是服務端,但是TIME_WAIT出現在負載均衡服務器上;它和DB的連接,它是客戶端,recycle對它並沒有什麼影響,關鍵是reuse

方案二:Web服務器首先關閉來自負載均衡服務器的連接 在這種情況下,Web服務器變成TIME_WAIT的重災區。負載均衡對Web服務器的連接,由Web服務器首先關閉連接,TIME_WAIT出現在Web服務器上;Web服務器對DB服務器的連接,由Web服務器關閉連接,TIME_WAIT也出現在它身上,此時:

負載均衡服務器上的配置: net.ipv4.tcp_tw_reuse:0 或者 1 都行,都沒有實際意義 net.ipv4.tcp_tw_recycle=0           //一定是關閉recycle

在Web服務器上的配置: net.ipv4.tcp_tw_reuse = 1       //這個配置主要影響的是Web服務器到DB服務器的連接複用 net.ipv4.tcp_tw_recycle=1      //由於在負載均衡和Web服務器之間並沒有NAT的網絡,可以考慮開啓recycle,加速由於負載均衡和Web服務器之間的連接造成的大量TIME_WAIT

問題1: 通常說的連接池可以複用連接,是不是意味着,需要等到上個連接time wait結束後才能再次使用? 所謂連接池複用,複用的一定是活躍的連接,所謂活躍,第一表明連接池裏的連接都是ESTABLISHED的,第二,連接池做爲上層應用,會有定時的心跳去保持連接的活躍性。既然連接都是活躍的,那就不存在有TIME_WAIT的概念了,在上篇裏也有提到,TIME_WAIT是在主動關閉連接的一方,在關閉連接後才進入的狀態。既然已經關閉了,那麼這條連接肯定已經不在連接池裏面了,即被連接池釋放了。

問題2: 作爲負載均衡的機器隨機端口使用完的情況下大量time_wait,不調整上面文中說的那三個參數,有其他的更好的方案嗎? 第一,隨機端口使用完,你可以通過調整/etc/sysctl.conf下的net.ipv4.ip_local_port_range配置,至少修改成 net.ipv4.ip_local_port_range=1024 65535,保證你的負載均衡服務器至少可以使用6萬個隨機端口,也即可以有6萬的反向代理到後端的連接,可以支持每秒1000的併發(想一想,因爲TIME_WAIT狀態會持續1分鐘後消失,所以一分鐘最多有6萬,每秒1000);如果這麼多端口都使用完了,也證明你應該加服務器了,或者,你的負載均衡服務器需要配置多個IP地址,或者,你的後端服務器需要監聽更多的端口和配置更多的IP(想一下socket的五元組)

第二,大量的TIME_WAIT,多大量?如果是幾千個,其實不用擔心,因爲這個內存和CPU的消耗有一些,但是是可以忽略的。

第三,如果真的量很大,上萬上萬的那種,可以考慮,讓後端的服務器主動關閉連接,如果後端服務器沒有外網的連接只有負載均衡服務器的連接(主要是沒有NAT網絡的連接),可以在後端服務器上配置tw_recycle,然後同時,在負載均衡服務器上,配置tw_reuse。

==============================簡單解釋下TCP狀態轉移================================

簡單來說 一端忘記close,將造成另一端大量的close_wait的狀態。 主動執行close的一端,在量特別大的情況下,對so_linger沒有做設置,將造成大量的time_wait狀態的連接。

TCP狀態轉移要點 TCP協議規定,對於已經建立的連接,網絡雙方要進行四次握手才能成功斷開連接,如果缺少了其中某個步驟,將會使連接處於假死狀態,連接本身佔用的資源不會被釋放。網絡服務器程序要同時管理大量連接,所以很有必要保證無用連接完全斷開,否則大量僵死的連接會浪費許多服務器資源

客戶端TCP狀態遷移: CLOSED -> SYN_SENT -> ESTABLISHED -> FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT -> CLOSED 服務器TCP狀態遷移: CLOSED -> LISTEN -> SYN收到 -> ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED 當客戶端開始連接時,服務器還處於LISTENING,客戶端發一個SYN包後,他就處於SYN_SENT狀態,服務器就處於SYS收到狀態,然後互相確認進入連接狀態ESTABLISHED.

相關狀態解釋 1) LISTENING狀態 服務啓動後首先處於偵聽(LISTENING)狀態。

2) ESTABLISHED狀態 ESTABLISHED的意思是建立連接。表示兩臺機器正在通信。

3) CLOSE_WAIT 對方主動關閉連接或者網絡異常導致連接中斷,這時我方的狀態會變成CLOSE_WAIT 此時我方要調用close()來使得連接正確關閉

4) TIME_WAIT 我方主動調用close()斷開連接,收到對方確認後狀態變爲TIME_WAIT,缺省爲240秒。TCP協議規定TIME_WAIT狀態會一直持續2MSL(即兩倍的分段最大生存期),以此來確保舊的連接狀態不會對新連接產生影響。處於TIME_WAIT狀態的連接佔用的資源不會被內核釋放,所以作爲服務器,在可能的情況下,儘量不要主動斷開連接,以減少TIME_WAIT狀態造成的資源浪費。

目前有一種避免TIME_WAIT資源浪費的方法,就是關閉socket的LINGER選項。但這種做法是TCP協議不推薦使用的,在某些情況下這個操作可能會帶來錯誤.

斷開連接的時候, 當發起主動關閉的左邊這方發送一個FIN過去後,右邊被動關閉的這方要回應一個ACK,這個ACK是TCP迴應的,而不是應用程序發送的,此時,被動關閉的一方就處於CLOSE_WAIT狀態了。如果此時被動關閉的這一方不再繼續調用closesocket,那麼他就不會發送接下來的FIN,導致自己老是處於CLOSE_WAIT。只有被動關閉的這一方調用了closesocket,纔會發送一個FIN給主動關閉的這一 方,同時也使得自己的狀態變遷爲LAST_ACK。

出現大量CLOSE_WAIT的原因很簡單,就是某一方在網絡連接斷開後,沒有檢測到這個錯誤,沒有執行closesocket,導致了這個狀態的實現,這在TCP/IP協議的狀態變遷圖上可以清楚看到。同時和這個相對應的還有一種叫TIME_WAIT的。一端的Socket調用close後,另一端的Socket沒有調用close

另外,把SOCKET的SO_LINGER設置爲0秒拖延(也就是立即關閉)在很多時候是有害處的。 還有,把端口設置爲可複用是一種不安全的網絡編程方法

當主動關閉的一方發送FIN到被動關閉這邊後,被動關閉這邊的TCP馬上回應一個ACK過去,同時向上面應用程序提交一個ERROR,導 致上面的SOCKET的send或者recv返回SOCKET_ERROR,正常情況下,如果上面在返回SOCKET_ERROR後調用了closesocket,那麼被動關閉的者一方的TCP就會發送一個FIN過去,自己的狀態就變遷到LAST_ACK. 

使用netstat -na命令即可知道到當前的TCP連接狀態。一般LISTEN、ESTABLISHED、TIME_WAIT是比較常見。

分析: time_wait過多這個問題主要因爲TCP的結束流程未走完,造成連接未釋放。現設客戶端主動斷開連接,流程如下:

Client 消息 Server close() ------ FIN -------> FIN_WAIT1 CLOSE_WAIT <----- ACK ------- FIN_WAIT2 close() <------ FIN ------ TIME_WAIT LAST_ACK

------ ACK -------> CLOSED CLOSED

如上圖所示,由於Server的Socket在客戶端已經關閉時而沒有調用關閉,造成服務器端的連接處在“掛起”狀態,而客戶端則處在等待應答的狀態上。此問題的典型特徵是:一端處於FIN_WAIT2 ,而另一端處於CLOSE_WAIT .

對於基於TCP的HTTP協議,關閉TCP連接的是Server端,這樣,Server端會進入TIME_WAIT狀態,可 想而知,對於訪問量大的Web Server,會存在大量的TIME_WAIT狀態,假如server一秒鐘接收1000個請求,那麼就會積壓240*1000=240,000個TIME_WAIT的記錄,維護這些狀態給Server帶來負擔。當然現代操作系統都會用快速的查找算法來管理這些TIME_WAIT,所以對於新的TCP連接請求,判斷是否hit中一個TIME_WAIT不會太費時間,但是有這麼多狀態要維護總是不好。

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