在服務器的平常維護過程當中,會常常用到下面的命令:
netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’
其中$NF表示最後一個字段
它會顯示例以下面的信息:
TIME_WAIT 814
CLOSE_WAIT 1
FIN_WAIT1 1
ESTABLISHED 634
SYN_RECV 2
LAST_ACK 1
經常使用的三個狀態是:ESTABLISHED 表示正在通訊,TIME_WAIT 表示主動關閉,CLOSE_WAIT 表示被動關閉。linux
再具體一點,四次揮手的交互過程以下:
客戶端先發送FIN,進入FIN_WAIT1狀態
服務端收到FIN,發送ACK,進入CLOSE_WAIT狀態,客戶端收到這個ACK,進入FIN_WAIT2狀態
服務端發送FIN,進入LAST_ACK狀態
客戶端收到FIN,發送ACK,進入TIME_WAIT狀態,服務端收到ACK,進入CLOSE狀態
客戶端TIME_WAIT持續2倍MSL時長,在linux體系中大概是60s,轉換成CLOSE狀態服務器
能不能發送完ACK以後不進入TIME_WAIT就直接進入CLOSE狀態呢?不行的,這個是爲了TCP協議的可靠性,因爲網絡緣由,ACK可能會發送失敗,那麼這個時候,被動一方會主動從新發送一次FIN,這個時候若是主動方在TIME_WAIT狀態,則還會再發送一次ACK,從而保證可靠性。那麼從這個解釋來講,2MSL的時長設定是能夠理解的,MSL是報文最大生存時間,若是從新發送,一個FIN+一個ACK,再加上不按期的延遲時間,大體是在2MSL的範圍。
若是服務器出了異常,百分之八九十都是下面兩種狀況:
1.服務器保持了大量TIME_WAIT狀態
2.服務器保持了大量CLOSE_WAIT狀態
由於linux分配給一個用戶的文件句柄是有限的,而TIME_WAIT和CLOSE_WAIT兩種狀態若是一直被保持,那麼意味着對應數目的通道就一直被佔着,一旦達到句柄數上限,新的請求就沒法被處理了,接着應用程序可能返回大量Too Many Open Files異常。
1)服務端的Time-wait過多
先來講一說長鏈接和短鏈接,在HTTP1.1協議中,有個 Connection 頭,Connection有兩個值,close和keep-alive,這個頭就至關於客戶端告訴服務端,服務端你執行完成請求以後,是關閉鏈接仍是保持鏈接。若是服務器使用的短鏈接,那麼每次客戶端請求後,服務器都會主動發送FIN關閉鏈接。最後進入time_wait狀態。可想而知,對於訪問量大的Web Server,會存在大量的TIME_WAIT狀態。讓服務器可以快速回收和重用那些TIME_WAIT的資源,能夠修改內核參數。
修改/etc/sysctl.conf以下:
#對於一個新建鏈接,內核要發送多少個 SYN 鏈接請求才決定放棄,不該該大於255,默認值是5,對應於180秒左右時間
net.ipv4.tcp_syn_retries=2
#net.ipv4.tcp_synack_retries=2
#表示當keepalive起用的時候,TCP發送keepalive消息的頻度。缺省是2小時,改成300秒
net.ipv4.tcp_keepalive_time=1200
net.ipv4.tcp_orphan_retries=3
#表示若是套接字由本端要求關閉,這個參數決定了它保持在FIN-WAIT-2狀態的時間
net.ipv4.tcp_fin_timeout=30
#表示SYN隊列的長度,默認爲1024,加大隊列長度爲8192,能夠容納更多等待鏈接的網絡鏈接數。
net.ipv4.tcp_max_syn_backlog = 4096
#表示開啓SYN Cookies。當出現SYN等待隊列溢出時,啓用cookies來處理,可防範少許SYN攻擊,默認爲0,表示關閉
net.ipv4.tcp_syncookies = 1cookie
#表示開啓重用。容許將TIME-WAIT sockets從新用於新的TCP鏈接,默認爲0,表示關閉
net.ipv4.tcp_tw_reuse = 1
#表示開啓TCP鏈接中TIME-WAIT sockets的快速回收,默認爲0,表示關閉
net.ipv4.tcp_tw_recycle = 1網絡
##減小超時前的探測次數
net.ipv4.tcp_keepalive_probes=5
##優化網絡設備接收隊列
net.core.netdev_max_backlog=3000
修改完以後執行/sbin/sysctl -p讓參數生效。socket
2)close_wait
若是一直保持在CLOSE_WAIT狀態,那麼只有一種狀況,就是在對方關閉鏈接以後服務器程序本身沒有進一步發出FIN信號,通常緣由都是TCP鏈接沒有調用關閉方法。換句話說,就是在對方鏈接關閉以後,程序裏沒有檢測到,或者程序壓根就忘記了這個時候須要關閉鏈接,因而這個資源就一直被程序佔着。這種狀況,經過服務器內核參數也沒辦法解決,服務器對於程序搶佔的資源沒有主動回收的權利,除非終止程序運行,必定程度上,可使用TCP的KeepAlive功能,讓操做系統替咱們自動清理掉CLOSE_WAIT鏈接。
可是實際上,仍是主要是由於咱們的程序代碼有問題,一般是以下問題:
當對方調用closesocket的時候,你的程序正在tcp
C代碼
int nRet = recv(s,….);
if (nRet == SOCKET_ERROR)
{
// closesocket(s);
return FALSE;
} 不少人就是忘記了那句closesocket
當主動關閉的一方發送FIN到被動關閉這邊後,被動關閉這邊的 TCP立刻迴應一個ACK過去,同時向上面應用程序提交一個ERROR,
致使上面的SOCKET的send或者recv返回SOCKET_ERROR,正常狀況下,若是上面在返回SOCKET_ERROR後調用了 closesocket,那麼被動關閉的者一方的TCP就會發送一個FIN過去,本身的狀態就變遷到LAST_ACK。優化