1.TCP握手定理
?
?
?2.TCP狀態
l? CLOSED:初始狀態,表示TCP連接是“關閉著的”或“未打開的”。
l? LISTEN?:表示服務器端的某個SOCKET處于監聽狀態,可以接受客戶端的連接。
l? SYN_RCVD?:表示服務器接收到了來自客戶端請求連接的SYN報文。在正常情況下,這個狀態是服務器端的SOCKET在建立TCP連接時的三次握手會話過程中的一個中間狀態,很短暫,基本上用netstat很難看到這種狀態,除非故意寫一個監測程序,將三次TCP握手過程中最后一個ACK報文不予發送。當TCP連接處于此狀態時,再收到客戶端的ACK報文,它就會進入到ESTABLISHED 狀態。
l? SYN_SENT?:這個狀態與SYN_RCVD 狀態相呼應,當客戶端SOCKET執行connect()進行連接時,它首先發送SYN報文,然后隨即進入到SYN_SENT 狀態,并等待服務端的發送三次握手中的第2個報文。SYN_SENT 狀態表示客戶端已發送SYN報文。
l? ESTABLISHED?:表示TCP連接已經成功建立。
l? FIN_WAIT_1?:這個狀態得好好解釋一下,其實FIN_WAIT_1 和FIN_WAIT_2 兩種狀態的真正含義都是表示等待對方的FIN報文。而這兩種狀態的區別是:FIN_WAIT_1狀態實際上是當SOCKET在ESTABLISHED狀態時,它想主動關閉連接,向對方發送了FIN報文,此時該SOCKET進入到FIN_WAIT_1 狀態。而當對方回應ACK報文后,則進入到FIN_WAIT_2 狀態。當然在實際的正常情況下,無論對方處于任何種情況下,都應該馬上回應ACK報文,所以FIN_WAIT_1 狀態一般是比較難見到的,而FIN_WAIT_2 狀態有時仍可以用netstat看到。
l? FIN_WAIT_2?:上面已經解釋了這種狀態的由來,實際上FIN_WAIT_2狀態下的SOCKET表示半連接,即有一方調用close()主動要求關閉連接。注意:FIN_WAIT_2 是沒有超時的(不像TIME_WAIT 狀態),這種狀態下如果對方不關閉(不配合完成4次揮手過程),那這個?FIN_WAIT_2 狀態將一直保持到系統重啟,越來越多的FIN_WAIT_2 狀態會導致內核crash。
l? TIME_WAIT?:表示收到了對方的FIN報文,并發送出了ACK報文。?TIME_WAIT狀態下的TCP連接會等待2*MSL(Max Segment Lifetime,最大分段生存期,指一個TCP報文在Internet上的最長生存時間。每個具體的TCP協議實現都必須選擇一個確定的MSL值,RFC 1122建議是2分鐘,但BSD傳統實現采用了30秒,Linux可以cat?/proc/sys/net/ipv4/tcp_fin_timeout看到本機的這個值),然后即可回到CLOSED 可用狀態了。如果FIN_WAIT_1狀態下,收到了對方同時帶FIN標志和ACK標志的報文時,可以直接進入到TIME_WAIT狀態,而無須經過FIN_WAIT_2狀態。(這種情況應該就是四次揮手變成三次揮手的那種情況)
l??CLOSING?:這種狀態在實際情況中應該很少見,屬于一種比較罕見的例外狀態。正常情況下,當一方發送FIN報文后,按理來說是應該先收到(或同時收到)對方的ACK報文,再收到對方的FIN報文。但是CLOSING 狀態表示一方發送FIN報文后,并沒有收到對方的ACK報文,反而卻也收到了對方的FIN報文。什么情況下會出現此種情況呢?那就是當雙方幾乎在同時close()一個SOCKET的話,就出現了雙方同時發送FIN報文的情況,這是就會出現CLOSING 狀態,表示雙方都正在關閉SOCKET連接。
l? CLOSE_WAIT?:表示正在等待關閉。怎么理解呢?當對方close()一個SOCKET后發送FIN報文給自己,你的系統毫無疑問地將會回應一個ACK報文給對方,此時TCP連接則進入到CLOSE_WAIT狀態。接下來呢,你需要檢查自己是否還有數據要發送給對方,如果沒有的話,那你也就可以close()這個SOCKET并發送FIN報文給對方,即關閉自己到對方這個方向的連接。有數據的話則看程序的策略,繼續發送或丟棄。簡單地說,當你處于CLOSE_WAIT 狀態下,需要完成的事情是等待你去關閉連接。
l? LAST_ACK?:當被動關閉的一方在發送FIN報文后,等待對方的ACK報文的時候,就處于LAST_ACK 狀態。當收到對方的ACK報文后,也就可以進入到CLOSED 可用狀態了。
#######################################################################################################################################
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 接收到它的連接終止請求的確認。 TIME_WAIT 兩個存在的理由: 1.可靠的實現tcp全雙工連接的終止; 2.允許老的重復分節在網絡中消逝。 CLOSED:不在連接狀態(這是為方便描述假想的狀態,實際不存在)
######################################################################################################################################
3.服務器大量TIME_WAIT和CLOSE_WAIT分析
#查看TCP狀態:netstat?-n?|?awk?'/^tcp/?{++S[$NF]}?END?{for(a?in?S)?print?a,?S[a]}'? ?
TIME_WAIT 814
CLOSE_WAIT 1
FIN_WAIT1 1
ESTABLISHED 634
SYN_RECV 2
LAST_ACK 1
常用的三個狀態是:ESTABLISHED 表示正在通信,TIME_WAIT 表示主動關閉,CLOSE_WAIT 表示被動關閉,Listen表示正在監聽可以接受客戶端連接。
#常見問題分析
1.服務器保持了大量TIME_WAIT狀態
2.服務器保持了大量CLOSE_WAIT狀態
因為linux分配給一個用戶的文件句柄是有限的,而TIME_WAIT和CLOSE_WAIT兩種狀態如果一直被保持,那么意味著對應數目的通道就一直被占著,而且是“占著茅坑不使勁”,一旦達到句柄數上限,新的請求就無法被處理了,接著就是大量Too Many Open Files異常,tomcat崩潰。
#############################################################################
tomcat環境下服務器文件句柄耗盡(Too Many Open Files)的問題排查
為什么會出現文件句柄耗盡的情況?
主要是因為linux在文件句柄的數目上有兩個級別的限制。一個是系統級別的總數限制,一個是針對用戶的限制。默認情況下每個用戶所能使用的句柄數是1024。一般情況下1024也夠用了,但是在大容量的系統上,特別是會頻繁使用網絡通信和文件IO的系統上,1024很快就被耗光了。所以首先我們要調整這個值。修改方法如下:
1.?ulimit?-a?查看當前用戶的文件句柄限制??
2.?用戶級別的句柄數限制修改?
修改?/etc/security/limits.conf?增加下面的代碼:??
用戶名(或者用*表示所有用戶)??soft?nofile?65535????
用戶名?hard?nofile?65535???
有兩種限制,一種是soft軟限制,在數目超過軟限制的時候系統會給出warning警告,但是達到hard硬限制的時候系統將拒絕或者異常了。??
修改之后可能需要重啟shell生效。??
3.?系統級別的句柄數限制修改
sysctl?-w?fs.file-max?65536??
或者??echo?"65536"?>?/proc/sys/fs/file-max??
兩者作用是相同的,前者改內核參數,后者直接作用于內核參數在虛擬文件系統(procfs,?psuedo?file?system)上對應的文件而已。??
可以用下面的命令查看新的限制??
sysctl?-a?|?grep?fs.file-max??
或者??cat?/proc/sys/fs/file-max??
4.修改內核參數??
/etc/sysctl.conf??
echo?"fs.file-max=65536"?>>?/etc/sysctl.conf??
sysctl?-p??
查看系統總限制?命令:cat?/proc/sys/fs/file-max????
查看整個系統目前使用的文件句柄數量命令:cat?/proc/sys/fs/file-nr???
查看某個進程開了哪些句柄?:lsof?-p?pid????
某個進程開了幾個句柄?:lsof?-p?pid?|wc?-l????
也可以看到某個目錄?/文件被什么進程占用了,顯示已打開該目錄或文件的所有進程信息?:lsof?path/filename? ?
具體這個值應該設置成多少?
優先級(Open File Descriptors):
soft limit < hard limit < kernel < 實現最大file descriptor數采用的數據結構所導致的限制
其實這個值倒是沒有具體限制,但是分配的值如果太大反而會影響系統性能,所以要根據具體應用調配權衡。
問題的解決方案:
首先當然是修改linux句柄數限制到一個合適的值。
然后就是應用本身的一個調整。有這么幾種情況:
1.數據庫連接池的優化。必須要使用連接池,否則句柄沒耗光數據庫就崩了。。。
2.抓取資源的時候有可能會用到HttpClient,盡量也應該使用連接池來控制連接數。
3.連接池設置的把握,建立連接超時時間,讀取超時時間,連接數目,等待時間,等都需要配置到一個合適的值,否則發揮不出連接池的性能。
###########################################################################################################
解決思路很簡單,就是讓服務器能夠快速回收和重用那些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?=?1??
#表示開啟重用。允許將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? ?
net.ipv4.tcp_fin_timeout
net.ipv4.tcp_keepalive_*
如果將大量CLOSE_WAIT的解決辦法總結為一句話那就是:查代碼。因為問題出在服務器程序里頭啊。
?