TCP的狀態(tài)轉(zhuǎn)換及生產(chǎn)問題實操
前文介紹了TCP協(xié)議主要的流程,包括建立連接、傳輸數(shù)據(jù)和斷開連接。如果大家認真閱讀了附圖,應(yīng)該可以看到在各個流程中套接字的狀態(tài)是在不斷變化的,不同的狀態(tài)標識了套集字所處的階段。
如圖1是TCP一個完整的狀態(tài)轉(zhuǎn)換圖,圖中包含了套接字的所有狀態(tài),以及發(fā)生狀態(tài)轉(zhuǎn)變的觸發(fā)條件??赡軙腥藛?,了解這些狀態(tài)有什么用呢?我們平時編程又用不到。
圖1 TCP狀態(tài)轉(zhuǎn)換圖
為了說明上述問題,我們從3個角度進行解釋,分別是各種狀態(tài)的含義、在系統(tǒng)層面如何查詢狀態(tài)和在實際生產(chǎn)中的應(yīng)用。
一、各種狀態(tài)的含義
在回答問題之前我們先具體了解一下各個狀態(tài)的含義。
- CLOSED:這個是套接字的初始狀態(tài),表示TCP連接是新建“未打開的”狀態(tài)或者已經(jīng)“關(guān)閉著的”。
- LISTEN :這個是服務(wù)端僅有的狀態(tài),表示服務(wù)器端的某個SOCKET處于監(jiān)聽狀態(tài),可以接受客戶端的連接。
- SYN_RCVD :表示服務(wù)器接收到了來自客戶端請求連接的SYN報文。在正常情況下,這個狀態(tài)我們可能觀察不到,因為這個狀態(tài)是服務(wù)器端的SOCKET在建立TCP連接時的三次握手會話過程中的一個中間狀態(tài),很短暫。
- SYN_SENT :這個狀態(tài)與SYN_RCVD 狀態(tài)相呼應(yīng),當客戶端SOCKET執(zhí)行connect()進行連接時,它首先發(fā)送SYN報文,然后隨即進入到SYN_SENT 狀態(tài),并等待服務(wù)端的發(fā)送三次握手中的第2個報文。SYN_SENT 狀態(tài)表示客戶端已發(fā)送SYN報文。
- ESTABLISHED :表示TCP連接已經(jīng)成功建立。
- FIN_WAIT_1 :這個狀態(tài)得好好解釋一下,其實FIN_WAIT_1 和FIN_WAIT_2 兩種狀態(tài)的真正含義都是表示等待對方的FIN報文。而這兩種狀態(tài)的區(qū)別是:FIN_WAIT_1狀態(tài)實際上是當SOCKET在ESTABLISHED狀態(tài)時,它想主動關(guān)閉連接,向?qū)Ψ桨l(fā)送了FIN報文,此時該SOCKET進入到FIN_WAIT_1 狀態(tài)。而當對方回應(yīng)ACK報文后,則進入到FIN_WAIT_2 狀態(tài)。當然在實際的正常情況下,無論對方處于任何種情況下,都應(yīng)該馬上回應(yīng)ACK報文,所以FIN_WAIT_1 狀態(tài)一般是比較難見到的,而FIN_WAIT_2 狀態(tài)有時仍可以用netstat看到。
- FIN_WAIT_2 :上面已經(jīng)解釋了這種狀態(tài)的由來,實際上FIN_WAIT_2狀態(tài)下的SOCKET表示半連接,即有一方調(diào)用close()主動要求關(guān)閉連接。注意:FIN_WAIT_2 是沒有超時的(不像TIME_WAIT 狀態(tài)),這種狀態(tài)下如果對方不關(guān)閉(不配合完成4次揮手過程),那這個 FIN_WAIT_2 狀態(tài)將一直保持到系統(tǒng)重啟,越來越多的FIN_WAIT_2 狀態(tài)會導(dǎo)致內(nèi)核crash。
- TIME_WAIT :表示收到了對方的FIN報文,并發(fā)送出了ACK報文。 TIME_WAIT狀態(tài)下的TCP連接會等待2*MSL(Max Segment Lifetime,大分段生存期,指一個TCP報文在Internet上的最長生存時間。)在Linux可以通過cat /proc/sys/net/ipv4/tcp_fin_timeout看到本機的這個值,然后即可回到CLOSED 可用狀態(tài)了。
- CLOSING :這種狀態(tài)在實際情況中應(yīng)該很少見,屬于一種比較罕見的例外狀態(tài)。正常情況下,當一方發(fā)送FIN報文后,按理來說是應(yīng)該先收到(或同時收到)對方的ACK報文,再收到對方的FIN報文。但是CLOSING 狀態(tài)表示一方發(fā)送FIN報文后,并沒有收到對方的ACK報文,反而卻也收到了對方的FIN報文。什么情況下會出現(xiàn)此種情況呢?那就是當雙方幾乎在同時close()一個SOCKET的話,就出現(xiàn)了雙方同時發(fā)送FIN報文的情況,這是就會出現(xiàn)CLOSING 狀態(tài),表示雙方都正在關(guān)閉SOCKET連接。
- CLOSE_WAIT :表示正在等待關(guān)閉。怎么理解呢?當對方close()一個SOCKET后發(fā)送FIN報文給自己,你的系統(tǒng)毫無疑問地將會回應(yīng)一個ACK報文給對方,此時TCP連接則進入到CLOSE_WAIT狀態(tài)。接下來呢,你需要檢查自己是否還有數(shù)據(jù)要發(fā)送給對方,如果沒有的話,那你也就可以close()這個SOCKET并發(fā)送FIN報文給對方,即關(guān)閉自己到對方這個方向的連接。有數(shù)據(jù)的話則看程序的策略,繼續(xù)發(fā)送或丟棄。簡單地說,當你處于CLOSE_WAIT 狀態(tài)下,需要完成的事情是等待你去關(guān)閉連接。
- LAST_ACK :當被動關(guān)閉的一方在發(fā)送FIN報文后,等待對方的ACK報文的時候,就處于LAST_ACK 狀態(tài)。當收到對方的ACK報文后,也就可以進入到CLOSED 可用狀態(tài)了。
二、狀態(tài)的監(jiān)控方法
前文已經(jīng)有提及,可以通過netstat命令查看TCP連接的狀態(tài)。圖2是一個簡單的例子,執(zhí)行該命令的時候不帶任何參數(shù)。
圖2 netstat執(zhí)行結(jié)果
由上圖可以看出,通過netstat可以看到每個TCP連接和UDP的狀態(tài)和詳細的IP地址等信息。該命令有很多參數(shù),通過不同的參數(shù)可以得到我們想要的內(nèi)容。下面我們舉幾個具體的例子。
1. 顯示所有端口信息
可以通過-a參數(shù)列出所有端口信息,而且可以附帶-t只列出TCP協(xié)議的,或者-u只列出UDP協(xié)議的端口信息。
- [root@itworld123~]# netstat -a # 列出所有端口
- [root@itworld123~]# netstat -at # 列出所有TCP端口
- [root@itworld123~]# netstat -au # 列出所有UDP端口
2. 顯示所有監(jiān)聽狀態(tài)的套接字
可以通過-l參數(shù)列出所有處于監(jiān)聽狀態(tài)的套接字。當然也可以結(jié)合-t或者-u參數(shù)獲取想要的信息。如下是獲取TCP處于監(jiān)聽狀態(tài)的套接字列表:
- root@itworld123:~# netstat -lu
圖3 監(jiān)聽狀態(tài)列表
3. 查看服務(wù)狀態(tài)
可以查看具體的服務(wù)的監(jiān)聽和套接字等狀態(tài)。例如下面命令用于查看ssh服務(wù)的狀態(tài):
- root@itworld123:~#netstat -antp | grep ssh
圖3 SSH狀態(tài)結(jié)果
4. 其它
當然,也可以通過shell腳本實現(xiàn)復(fù)雜的查詢,比如下面用于統(tǒng)計ESTABLISHED狀態(tài)的數(shù)量。
- netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
netstat命令功能非常強大,由于篇幅問題,本文只能拋磚引玉,更多功能可以man一下看看,這里就不過多解釋。
三、實際生產(chǎn)環(huán)境的意義
前面啰嗦了一大堆,我們回到正題,了解這些狀態(tài)到底有什么用呢?我們知道Linux操作系統(tǒng)對文件句柄的總量是有限制的,套接字也屬于文件句柄,因此也是有限制的。了解套接字的狀態(tài)有助于我們了解服務(wù)器是否有隱患或者性能瓶頸。
說到這,可能有的同學(xué)還是不明白,我們舉個簡單的例子。假設(shè)一臺服務(wù)器最多有6萬個句柄,如果由于某種業(yè)務(wù)場景,在服務(wù)器端出現(xiàn)大量的TIME_WAIT,此時這些套接字是無法馬上釋放,也就是無法馬上被重復(fù)使用,但仍然占用6萬句柄的名額。這塊,隨著時間的推移,可能會耗盡所有句柄,從而導(dǎo)致有新的連接請求是服務(wù)器端無法響應(yīng)的問題。
為了讓大家更形象的理解這些狀態(tài)在實際生產(chǎn)中的意義,我們舉幾個實際生產(chǎn)中遇到問題的例子。
1. 服務(wù)器端大量TIME_WAIT
(1) 現(xiàn)象描述
某對象存儲服務(wù),在監(jiān)控系統(tǒng)發(fā)現(xiàn)有大量的TIME_WAIT。經(jīng)確認該服務(wù)器是一臺新上架接入的服務(wù)器。經(jīng)反復(fù)確認,具備相同功能的同集群的其它服務(wù)器工作都正常,并不存在大量TIME_WAIT的情況。
(2) 問題分析
結(jié)合協(xié)議我們知道主動關(guān)閉方會處于該狀態(tài),而且TIME_WAIT狀態(tài)下的TCP連接會等待2*MSL。因此我們查看系統(tǒng)配置cat /proc/sys/net/ipv4/tcp_fin_timeout,發(fā)現(xiàn)是默認值。因此,確定是等待時間太長,導(dǎo)致套接字無法被利用所致。
(3) 問題解決
通過調(diào)整內(nèi)核參數(shù)解決,打開文件/etc/sysctl.conf,編輯文件,加入以下內(nèi)容:
- net.ipv4.tcp_syncookies = 1
- net.ipv4.tcp_tw_reuse = 1
- net.ipv4.tcp_tw_recycle = 1
- net.ipv4.tcp_fin_timeout = 30
然后執(zhí)行/sbin/sysctl -p讓參數(shù)生效。
上述內(nèi)容的含義具體如下:
- net.ipv4.tcp_syncookies = 1表示開啟SYN Cookies。當出現(xiàn)SYN等待隊列溢出時,啟用cookies來處理,可防范少量SYN攻擊,默認為0,表示關(guān)閉;
- net.ipv4.tcp_tw_reuse = 1表示開啟重用。允許將TIME-WAIT sockets重新用于新的TCP連接,默認為0,表示關(guān)閉;
- net.ipv4.tcp_tw_recycle = 1表示開啟TCP連接中TIME-WAIT sockets的快速回收,默認為0,表示關(guān)閉。
- net.ipv4.tcp_fin_timeout修改系統(tǒng)默認的TIMEOUT時間
2. 服務(wù)器端大量ESTABLISHED
(1) 問題描述
某Tomcat服務(wù)器出現(xiàn)大量ESTABLISHED連接。
(2) 問題分析
根據(jù)協(xié)議狀態(tài)轉(zhuǎn)換情況,初步推斷是tomcat服務(wù)器回收session時出了問題,這個一般都跟服務(wù)器的Timeout設(shè)置有聯(lián)系。
查看tomcat的配置文件 server.xml
- <Connector port="8080" protocol="HTTP/1.1"
- connectionTimeout="20000"
- redirectPort="8443" URIEncoding="UTF-8" />
- *****
我們重點關(guān)注一下connectionTimeout,這個配置導(dǎo)致建立一個socket連接后,如果一直沒有收到客戶端的FIN,也沒有數(shù)據(jù)過來,那么此連接也必須等到10s后,才能被超時釋放。由于服務(wù)器并發(fā)量大,而該超時時間有長,導(dǎo)致連接釋放嚴重滯后,因此出現(xiàn)大量的ESTABLISHED連接。
(3) 問題解決
分析上述問題后,我們有針對性的作出如下修改。
- connectionTimeout="20000" 改為 connectionTimeout="100"
- acceptCount="100"改為acceptCount="5000"
修改后問題解決。
實際的例子還很多,但萬變不離其宗,需要我們熟悉TCP協(xié)議和狀態(tài)轉(zhuǎn)換,這樣在實際生產(chǎn)中遇到問題就可以有理有據(jù)的進行分析,然后輕松解決。