最近遇到的一個關於socket.close的問題,在某個應用服務器出現的情況(執行netstat -np | grep tcp): java
tcp 0 0 10.224.122.16:50158 10.224.112.58:8788 CLOSE_WAITlinux
tcp 0 0 10.224.122.16:37655 10.224.112.58:8788 CLOSE_WAITubuntu
tcp 1 0 127.0.0.1:32713 127.0.0.1:8080 CLOSE_WAITwindows
tcp 38 0 10.224.122.16:34538 10.224.125.42:443 CLOSE_WAIT瀏覽器
tcp 38 0 10.224.122.16:33394 10.224.125.42:443 CLOSE_WAITtomcat
tcp 1 0 10.224.122.16:18882 10.224.125.10:80 CLOSE_WAIT服務器
tcp 1 0 10.224.122.16:18637 10.224.125.10:80 CLOSE_WAIT網絡
tcp 1 0 10.224.122.16:19655 10.224.125.12:80 CLOSE_WAIT架構
........................................併發
總共出現了200個CLOSE_WAIT的socket.並且這些socket長時間得不到釋放.下面咱們來看看爲何會出現這種大量socket的CLOSE_WAIT狀況
首先咱們要搞清楚的是,這個socket是誰發起的,咱們能夠看到122.16這臺機器開了不少端口,並且端口號都很大,125.12 或者125.10上的端口都是很常見服務器端口,因此122.16上這麼多CLOSE_WAIT
的socket是由122.16開啓的,換句話說這臺機器是傳統的客戶端,它會主動的請求其餘機器的服務端口.
要搞清楚爲何會出現CLOSE_WAIT,那麼首先咱們必需要清楚CLOSE_WAIT的機制和原理.
假設咱們有一個client, 一個server.
當client主動發起一個socket.close()這個時候對應TCP來講,會發生什麼事情呢?以下圖所示.

client首先發送一個FIN信號給server, 這個時候client變成了FIN_WAIT_1的狀態, server端收到FIN以後,返回ACK,而後server端的狀態變成了CLOSE_WAIT.
接着server端須要發送一個FIN給client,而後server端的狀態變成了LAST_ACK,接着client返回一個ACK,而後server端的socket就被成功的關閉了.
從這裏能夠看到,若是由客戶端主動關閉一連接,那麼客戶端是不會出現CLOSE_WAIT狀態的.客戶端主動關閉連接,那麼Server端將會出現CLOSE_WAIT的狀態.
而咱們的服務器上,是客戶端socket出現了CLOSE_WAIT,因而可知這個是因爲server主動關閉了server上的socket.
那麼當server主動發起一個socket.close(),這個時候又發生了一些什麼事情呢.

從圖中咱們能夠看到,若是是server主動關閉連接,那麼Client則有可能進入CLOSE_WAIT,若是Client不發送FIN包,那麼client就一直會處在CLOSE_WAIT狀態(後面咱們能夠看到有參數能夠調整這個時間).
那麼如今咱們要搞清楚的是,在第二中場景中,爲何Client不發送FIN包給server.要搞清楚這個問題,咱們首先要搞清楚server是怎麼發FIN包給client的,其實server就是調用了
socket.close方法而已,也就是說若是要client發送FIN包,那麼client就必須調用socket.close,不然就client就一直會處在CLOSE_WAIT(但事實上不一樣操做系統這點的實現還不同,
在ahuaxuan(ahuaxuan.iteye.com)的例子中也出現了這樣的case).
下面咱們來作幾個實驗
實驗一:
環境:
服務器端:win7+tomcat,tomcat的keep-alive的時間爲默認的15s.
客戶端:mac os
實驗步驟:服務器啓動後,客戶端向服務器發送一個get請求,而後客戶端阻塞,等待服務器端的socket超時.經過netstat -np tcp能夠看到的狀況是發送get請求時,服務器和客戶端連接是ESTABLISHED, 15s以後,客戶端變成了CLOSE_WAIT,而服務器端變成了FIN_WAIT_2.這一點也在咱們的預料之中,而這個時候因爲客戶端線程阻塞,客戶 端socket空置在那裏,不作任何操做,2分鐘事後,這個連接不論是在win7上,仍是在mac os都看不到了.可見,FIN_WAIT_2或者CLOSE_WAIT有一個timeout.在後面的實驗,能夠證實,在這個例子中,實際上是 FIN_WAIT_2有一個超時,一旦過了2分鐘,那麼win7會發一個RST給mac os要求關閉雙方的socket.
實驗二
服務器端:ubuntu9.10+tomcat,tomcat的keep-alive的時間爲默認的15s.
客戶端:mac os
實驗步驟:服務器啓動後,客戶端向服務器發送一個get請求,而後客戶端阻塞,等待服務器端的socket超時.經過netstat -np tcp(ubuntu使用netstat -np|grep tcp)能夠看到的狀況是發送get請求時,服務器和客戶端連接是ESTABLISHED, 15s以後,客戶端變成了CLOSE_WAIT,而服務器端變成了FIN_WAIT_2.這一點也也在咱們的預料之中,而這個時候因爲客戶端線程阻塞,客 戶端socket空置在那裏,不作任何操做,1分鐘事後,ubuntu上的那個socket不見了,可是mac os上的socket還在,並且仍是CLOSE_WAIT,這說明,FIN_WAIT_2確實有一個超時時間,win7上的超時操做能夠關閉mac os上的socket,而ubuntu上的FIN_WAIT_2超時操做卻不能關閉mac os上的socket(其狀一直是CLOSE_WAIT).
實驗三
服務器端:mac os+tomcat,tomcat的keep-alive的時間爲默認的15s.
客戶端:mac os
實驗步驟:服務器啓動後,客戶端向服務器發送一個get請求,而後客戶端阻塞,等待服務器端的socket超時.經過netstat -np tcp能夠看到的狀況是發送get請求時,服務器和客戶端連接是ESTABLISHED, 15s以後,客戶端變成了CLOSE_WAIT,而服務器端變成了FIN_WAIT_2.這一點也在咱們的預料之中,而這個時候因爲客戶端線程阻塞,客戶 端socket空置在那裏,不作任何操做,4分鐘事後,mac os服務器端上的那個socket不見了,可是mac os客戶端上的socket還在,並且仍是CLOSE_WAIT,這說明,FIN_WAIT_2確實有一個超時時間,win7上的超時操做能夠關閉mac os上的socket,而ubuntu和mac os上的FIN_WAIT_2超時操做卻不能關閉mac os上的socket.
總結, 當服務器的內核不同上FIN_WAIT_2的超時時間和操做是不同的.
經查:控制FIN_WAIT_2的參數爲:
/proc/sys/net/ipv4/tcp_fin_timeout
如 果套接字由本端要求關閉,這個參數決定了它保持在FIN-WAIT-2狀態的時間。對端能夠出錯並永遠不關閉鏈接,甚至意外當機。缺省值是60秒。2.2 內核的一般值是180秒,你能夠按這個設置,但要記住的是,即便你的機器是一個輕載的WEB服務器,也有由於大量的死套接字而內存溢出的風險,FIN- WAIT-2的危險性比FIN-WAIT-1要小,由於它最多隻能吃掉1.5K內存,可是它們的生存期長些。參見tcp_max_orphans。
實驗四
服務器端:ubuntu9.10+tomcat,tomcat的keep-alive的時間爲默認的15s.
客戶端:mac os
實驗步驟:服務器啓動後,客戶端向服務器發送一個get請求,而後關閉客戶端關閉socket.經過netstat -np tcp能夠看到的狀況是發送get請求時,服務器和客戶端連接是ESTABLISHED, 客戶端拿到數據以後,客戶端變成了TIME_WAIT,而服務器端變成了已經看不到這個socket了.這一點也也在咱們的預料之中,誰主動關閉連接,那 麼誰就須要進入TIME_WAIT狀態(除非他的FIN_WAIT_2超時了),大約1分鐘以後這個socket在客戶端也消失了.
實驗證實TIME_WAIT的狀態會存在一段時間,並且在這個時間端裏,這個FD是不能被回收的.
可是咱們的問題是客戶端有不少CLOSE_WAIT,並且咱們的服務器不是windows,而是linux,因此CLOSE_WAIT有沒有超時時間呢,確定有,並且默認狀況下這個超時時間應該是比較大的.不然不會一會兒看到兩百個CLOSE_WAIT的狀態.
客戶端解決方案:
1.因爲socket.close()會致使FIN信號,而client的socket CLOSE_WAIT就是由於該socket該關的時候,咱們沒有關,因此咱們須要一個線程池來檢查空閒鏈接中哪些進入了超時狀態(idleTIME),但進入超時
的socket未必是CLOSE_WAIT的狀態的.不過若是咱們把空閒超時的socket關閉,那麼CLOSE_WAIT的狀態就會消失.(問 題:像HttpClient這樣的工具包中,若是要檢查連接池,那麼則須要鎖定整個池,而這個時候,用戶請求獲取connection的操做只能等待,在 高併發的時候會形成程序響應速度降低,具體參考IdleConnectionTimeoutThread.java(HttpClient3.1))
2.經查,其實有參數能夠調整CLOSE_WAIT的持續時間,若是咱們改變這個時間,那麼可讓CLOSE_WAIT只保持很短的時間(固然這個參數不僅做用在CLOSE_WAIT上,縮短這個時間可能會帶來其餘的影響).在客戶端機器上修改以下:
sysctl -w net.ipv4.tcp_keepalive_time=60(缺省是2小時,如今改爲了60秒)
sysctl -w net.ipv4.tcp_keepalive_probes=2
sysctl -w net.ipv4.tcp_keepalive_intvl=2
咱們將CLOSE_WAIT的檢查時間設置爲30s,這樣一個CLOSE_WAIT只會存在30S.
3. 固然,最重要的是咱們要檢查客戶端連接的空閒時間,空閒時間能夠由客戶端自行定義,好比idleTimeout,也可由服務器來決定,服務器只須要每次在 response.header中加入一個頭信息,好比說名字叫作timeout頭,固然通常狀況下咱們會用keep-alive這個頭字段, 若是服務器設置了該字段,那麼客戶端拿到這個屬性以後,就知道本身的connection最大的空閒時間,這樣不會因爲服務器關閉socket,而致使客 戶端socket一直close_wait在那裏.
服務器端解決方案
4.前面講到客戶端出現CLOSE_WAIT是因爲服務器端Socket的讀超時,也是TOMCAT中的keep-alive參數.那麼若是咱們把這個超時時間設置的長點,會有什麼影響?
若是咱們的tomcat既服務於瀏覽器,又服務於其餘的 APP,並且咱們把connection的keep-alive時間設置爲10分鐘,那麼帶來的後果是瀏覽器打開一個頁面,而後這個頁面一直不關閉,那麼 服務器上的socket也不能關閉,它所佔用的FD也不能服務於其餘請求.若是併發一高,很快服務器的資源將會被耗盡.新的請求再也進不來. 那麼若是把keep-alive的時間設置的短一點呢,好比15s? 那麼其餘的APP來訪問這個服務器的時候,一旦這個socket, 15s以內沒有新的請求,那麼客戶端APP的socket將出現大量的CLOSE_WAIT狀態.
因此若是出現這種狀況,建議將你的server分開部署,服務於browser的部署到單獨的JVM實例上,保持keep-alive爲15s,而服務於架構中其餘應用的功能部署到另外的JVM實例中,而且將keep-alive的時間設置的更
長,好比說1個小時.這樣客戶端APP創建的connection,若是在一個小時以內都沒有重用這條connection,那麼客戶端的 socket纔會進入CLOSE_WAIT的狀態.針對不一樣的應用場景來設置不一樣的keep-alive時間,能夠幫助咱們提升程序的性能.
5.若是咱們的應用既服務於瀏覽器,又服務於其餘的APP,那麼咱們還有一個終極解決方案.
那就是配置多個connector, 以下:
<!-- for browser -->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!-- for other APP -->
<Connector port="8081" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" keepAliveTimeout="330000" />
訪問的時候,瀏覽器使用8080端口,其餘的APP使用8081端口.這樣能夠保證瀏覽器請求的socket在15s以內若是沒有再次使用,那麼 tomcat會主動關閉該socket,而其餘APP請求的socket在330s以內沒有使用,才關閉該socket,這樣作能夠大大減小其餘APP上 出現CLOSE_WAIT的概率.
你必定會問,若是我不設置keepAliveTimeout又怎麼樣呢,反正客戶端有idleTimeout,客戶端的close_wait不會持 續太長時間,請注意看上圖中標紅的地方,一個是close_wait,還有一個是time_wait狀態,也就是說誰主動發起請求,那麼它將會最終進入 time_wait狀態,聽說windows上這個time_wait將持續4分鐘,我在linux上的測試代表,linux上它大概是60s左右,也就 是說高併發下,也就是服務器也須要過60s左右才能真正的釋放這個FD.因此咱們若是提供http服務給其餘APP,那麼咱們最好讓客戶端優先關閉 socket,也就是將客戶端的idleTimeout設置的比server的keepalivetimeout小一點.這樣保證time_wait出現 在客戶端. 而不是資源較爲緊張的服務器端.
總結:
本文中ahuaxuan給你們揭示了TCP層client和server端socket關閉的通常流程,而且指出異常狀況下client和server端 各自會發生的狀況,包含了在不一樣平臺上出現了的不一樣狀況, 同時說明了在應用層上咱們能夠作什麼樣的邏輯來保證socket關閉時對server端帶來最小的影響.
下面是網上找到的一些資料:
寫道
/proc/sys/net/ipv4/tcp_keepalive_time 當keepalive起用的時候,TCP發送keepalive消息的頻度。缺省是2小時。 /proc/sys/net/ipv4/tcp_keepalive_intvl 當探測沒有確認時,從新發送探測的頻度。缺省是75秒。 /proc /sys/net/ipv4/tcp_keepalive_probes 在認定鏈接失效以前,發送多少個TCP的keepalive探測包。缺省值是 9。這個值乘以tcp_keepalive_intvl以後決定了,一個鏈接發送了keepalive以後能夠有多少時間沒有迴應。/proc/sys/net/ipv4/tcp_max_orphans系 統中最多有多少個TCP套接字不被關聯到任何一個用戶文件句柄上。若是超過這個數字,孤兒鏈接將即刻被複位並打印出警告信息。這個限制僅僅是爲了防止簡單 的DoS攻擊,你絕對不能過度依靠它或者人爲地減少這個值,更應該增長這個值(若是增長了內存以後)。This limit exists only to prevent simple DoS attacks, you _must_ not rely on this or lower the limit artificially, but rather increase it (probably, after increasing installed memory), if network conditions require more than default value, and tune network services to linger and kill such states more aggressively. 讓我再次提醒你:每一個孤兒套接字最多可以吃掉你64K不可交換的內存。/proc/sys/net/ipv4/tcp_orphan_retries本端試圖關閉TCP鏈接以前重試多少次。缺省值是7,至關於50秒~16分鐘(取決於RTO)。若是你的機器是一個重載的WEB服務器,你應該考慮減低這個值,由於這樣的套接字會消耗不少重要的資源。參見tcp_max_orphans。/proc/sys/net/ipv4/tcp_max_syn_backlog記 錄的那些還沒有收到客戶端確認信息的鏈接請求的最大值。對於有128M內存的系統而言,缺省值是1024,小內存的系統則是128。若是服務器不堪重負,試 試提升這個值。注意!若是你設置這個值大於1024,最好同時調整include/net/tcp.h中的TCP_SYNQ_HSIZE,以保證 TCP_SYNQ_HSIZE*16 ≤tcp_max_syn_backlo,而後從新編譯內核。/proc/sys/net/ipv4/tcp_max_tw_buckets系 統同時保持timewait套接字的最大數量。若是超過這個數字,time-wait套接字將馬上被清除並打印警告信息。這個限制僅僅是爲了防止簡單的 DoS攻擊,你絕對不能過度依靠它或者人爲地減少這個值,若是網絡實際須要大於缺省值,更應該增長這個值(若是增長了內存以後)。