TCP 连接状态
TCP十一種狀態
全部11種狀態
? ? 1. 客戶端獨有的:(1)SYN_SENT (2)FIN_WAIT1 (3)FIN_WAIT2 (4)CLOSING (5)TIME_WAIT 。
? ? 2. 服務器獨有的:(1)LISTEN (2)SYN_RCVD (3)CLOSE_WAIT (4)LAST_ACK 。
? ? 3. 共有的:(1)CLOSED (2)ESTABLISHED 。
各個狀態的意義如下:?
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接收到連接中斷請求的確認; CLOSED - 沒有任何連接狀態;
各個狀態詳解:
LISTENING :偵聽來自遠方的TCP端口的連接請求 .
首先服務端需要打開一個 socket 進行監聽,狀態為LISTEN。 有提供某種服務才會處于LISTENING狀態, TCP狀態變化就是某個端口的狀態變化,提供一個服務就打開一個端口, 例如:提供www服務默認開的是80端口,提供ftp服務默認的端口為21,當提供的服務沒有被連接時就處于LISTENING狀態。 FTP服務啟動后首先處于偵聽(LISTENING)狀態。處于偵聽LISTENING狀態時,該端口是開放的,等待連接,但還沒有被連接。就像你房子的門已經敞開的,但還沒有人進來。 看LISTENING狀態最主要的是看本機開了哪些端口,這些端口都是哪個程序開的,關閉不必要的端口是保證安全的一個非常重要的方面, 服務端口都對應一個服務(應用程序),停止該服務就關閉了該端口,例如要關閉21端口只要停止IIS服務中的FTP服務即可。關于這方面的知識請參閱其它文章。 如果你不幸中了服務端口的木馬,木馬也開個端口處于LISTENING狀態。
SYN-SENT: 客戶端SYN_SENT狀態:
在客戶端發送連接請求后,等待匹配的連接請求: 客戶端通過應用程序調用connect進行active open.于是客戶端tcp發送一個SYN以請求建立一個連接.之后狀態置為SYN_SENT. /*The socket is actively attempting to establish a connection. 在發送連接請求后等待匹配的連接請求 */當請求連接時客戶端首先要發送同步信號給要訪問的機器,此時狀態為SYN_SENT,如果連接成功了就變為ESTABLISHED,正常情況下SYN_SENT狀態非常短暫。 例如要訪問網站http://www.baidu.com,如果是正常連接的話, 用TCPView觀察 IEXPLORE .EXE(IE)建立的連接會發現很快從SYN_SENT變為ESTABLISHED,表示連接成功。SYN_SENT狀態快的也許看不到。如果發現有很多SYN_SENT出現,那一般有這么幾種情況, 一是你要訪問的網站不存在或線路不好, 二是用掃描軟件掃描一個網段的機器,也會出出現很多SYN_SENT, 另外就是可能中了病毒了,例如中了"沖擊波",病毒發作時會掃描其它機器,這樣會有很多SYN_SENT出現。
SYN-RECEIVED: 服務器端狀態SYN_RCVD
再收到和發送一個連接請求后等待對方對連接請求的確認。當服務器收到客戶端發送的同步信號時,將標志位ACK和SYN置1發送給客戶端,此時服務器端處于SYN_RCVD狀態, 如果連接成功了就變為ESTABLISHED,正常情況下SYN_RCVD狀態非常短暫。如果發現有很多SYN_RCVD狀態,那你的機器有可能被SYN Flood的DoS(拒絕服務攻擊)攻擊了。
SYN Flood的攻擊原理是:
在進行三次握手時,攻擊軟件向被攻擊的服務器發送SYN連接請求(握手的第一步),但是這個地址是偽造的, 如攻擊軟件隨機偽造了51.133.163.104、65.158.99.152等等地址。 服務器 在收到連接請求時將標志位 ACK和 SYN 置1發送給客戶端(握手的第二步), 但是這些客戶端的IP地址都是偽造的,服務器根本找不到客戶機,也就是說握手的第三步不可能完成。 這種情況下服務器端一般會重試(再次發送SYN+ACK給客戶端)并等待一段時間后丟棄這個未完成的連接,這段時間的長度我們稱為SYN Timeout, 一般來說這個時間是分鐘的數量級(大約為30秒-2分鐘); 一個用戶出現異常導致服務器的一個線程等待1分鐘并不是什么很大的問題, 但如果有一個惡意的攻擊者大量模擬這種情況,服務器端將為了維護一個非常大的半連接列表而消耗非常多的資源----數以萬計的半連接, 即使是簡單的保存并遍歷也會消耗非常多的 CPU 時間和內存,何況還要不斷對這個列表中的IP進行SYN+ACK的重試。 此時從正常客戶的角度看來,服務器失去響應,這種情況我們稱做: 服務器端受到了SYN Flood攻擊(SYN洪水攻擊 )
ESTABLISHED:代表一個打開的連接。
ESTABLISHED狀態是表示兩臺機器正在傳輸數據,觀察這個狀態最主要的就是看哪個程序正在處于ESTABLISHED狀態。 服務器出現很多 ESTABLISHED狀態: netstat -nat |grep 9502或者使用lsof -i:9502可以檢測到。 當客戶端未主動close的時候就斷開連接:即客戶端發送的FIN丟失或未發送。 這時候若客戶端斷開的時候發送了FIN包,則服務端將會處于CLOSE_WAIT狀態; 這時候若客戶端斷開的時候未發送FIN包,則服務端處還是顯示ESTABLISHED狀態; 結果客戶端重新連接服務器。 而新連接上來的客戶端(也就是剛才斷掉的重新連上來了)在服務端肯定是ESTABLISHED; 如果客戶端重復的上演這種情況, 那么服務端將會出現大量的假的ESTABLISHED連接和CLOSE_WAIT連接。 最終結果就是新的其他客戶端無法連接上來,但是利用netstat還是能看到一條連接已經建立,并顯示ESTABLISHED,但始終無法進入程序代碼。
FIN-WAIT-1: 等待遠程TCP連接中斷請求,或先前的連接中斷請求的確認
主動關閉(active close)端應用程序調用close,于是其TCP發出FIN請求主動關閉連接,之后進入FIN_WAIT1狀態. /* The socket is closed, and the connection is shutting down. 等待遠程TCP的連接中斷請求,或先前的連接中斷請求的確認 */
FIN-WAIT-2:從遠程TCP等待連接中斷請求
主動關閉端接到ACK后,就進入了FIN-WAIT-2 ./* Connection is closed, and the socket is waiting for a shutdown from the remote end. 從遠程TCP等待連接中斷請求 */
這就是著名的半關閉的狀態了,這是在關閉連接時,客戶端和服務器兩次握手之后的狀態。在這個狀態下,應用程序還有接受數據的能力,但是已經無法發送數據,但是也有一種可能是,客戶端一直處于FIN_WAIT_2狀態,而服務器則一直處于WAIT_CLOSE狀態,而直到應用層來決定關閉這個狀態。
CLOSE-WAIT:等待從本地用戶發來的連接中斷請求
socket端口如何解除bind:http://blog.csdn.net/awu999328/article/details/24473033
linux 下端口close_wait 過多:http://blog.csdn.net/natureice/article/details/6439226
被動關閉(passive close)端TCP接到FIN后,就發出ACK以回應FIN請求(它的接收也作為文件結束符傳遞給上層應用程序),并進入CLOSE_WAIT. /* The remote end has shut down, waiting for the socket to close. 等待從本地用戶發來的連接中斷請求 */
對方主動關閉連接或者網絡異常導致連接中斷,這時我方的狀態會變成CLOSE_WAIT 此時我方要調用close()來使得連接正確關閉
CLOSING:等待遠程TCP對連接中斷的確認
比較少見./* Both sockets are shut down but we still don't have all our data sent. 等待遠程TCP對連接中斷的確認 */
LAST-ACK:等待原來的發向遠程TCP的連接中斷請求的確認
被動關閉端一段時間后,接收到文件結束符的應用程序將調用CLOSE關閉連接。這導致它的TCP也發送一個 FIN,等待對方的ACK.就進入了LAST-ACK . /* The remote end has shut down, and the socket is closed. Waiting for acknowledgement. 等待原來發向遠程TCP的連接中斷請求的確認 */
TIME-WAIT:等待足夠的時間以確保遠程TCP接收到連接中斷請求的確認
在主動關閉端接收到FIN后,TCP就發送ACK包,并進入TIME-WAIT狀態。/* The socket is waiting after close to handle packets still in the network.等待足夠的時間以確保遠程TCP接收到連接中斷請求的確認 */
TIME_WAIT等待狀態,這個狀態又叫做2MSL狀態,說的是在TIME_WAIT2發送了最后一個ACK數據報以后,要進入TIME_WAIT狀態,這個狀態是防止最后一次握手的數據報沒有傳送到對方那里而準備的(注意這不是四次握手,這是第四次握手的保險狀態)。這個狀態在很大程度上保證了雙方都可以正常結束,但是,問題也來了。
由于插口的2MSL狀態(插口是IP和端口對的意思,socket),使得應用程序在2MSL時間內是無法再次使用同一個插口的,對于客戶程序還好一些,但是對于服務程序,例如httpd,它總是要使用同一個端口來進行服務,而在2MSL時間內,啟動httpd就會出現錯誤(插口被使用)。為了避免這個錯誤,服務器給出了一個平靜時間的概念,這是說在2MSL時間內,雖然可以重新啟動服務器,但是這個服務器還是要平靜的等待2MSL時間的過去才能進行下一次連接。
我方主動調用close()斷開連接,收到對方確認后狀態變為TIME_WAIT。TCP協議規定TIME_WAIT狀態會一直持續2MSL(即兩倍的分段最大生存期),以此來確保舊的連接狀態不會對新連接產生影響。處于TIME_WAIT狀態的連接占用的資源不會被內核釋放,所以作為服務器,在可能的情況下,盡量不要主動斷開連接,以減少TIME_WAIT狀態造成的資源浪費。
CLOSED:沒有任何連接狀態
被動關閉端在接受到ACK包后,就進入了closed的狀態。連接結束./* The socket is not being used. 沒有任何連接狀態 */
TCP狀態遷移
大家對netstat -a命令很熟悉,但是,你注意到STATE一欄沒,基本上顯示著established,time_wait,close_wait等,這些到底是 什么意思呢?
大家很明白TCP初始化連接三次握手吧:發SYN包,然后返回SYN/ACK包,再發ACK包,連接正式建立。但是這里有點出入,當請求者收到SYS /ACK包后,就開始建立連接了,而被請求者第三次握手結束后才建立連接。但是大家明白關閉連接的工作原理嗎?關閉連接要四次握手:發FIN包,ACK 包,FIN包,ACK包,四次握手!!為什么呢,因為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.
當客戶端請求關閉連接時,客戶端發送一個FIN包后,客戶端就進入FIN_WAIT_1狀態,等待對方的確認包,
服務器發送一個ACK包給客戶,客戶端收到ACK包后結束FIN_WAIT_1狀態,進入FIN_WAIT_2狀態,等待服務器發過來的關閉請求,
服務器發一個FIN包后,進入CLOSE_WAIT狀態,
當客戶端收到服務器的FIN包,FIN_WAIT_2狀態就結束,然后給服務器端的FIN包給以一個確認包,客戶端這時進入TIME_WAIT,
當服務器收到確認包后,CLOSE_WAIT狀態結束了,
這時候服務器端真正的關閉了連接.但是客戶端還在TIME_WAIT狀態下,
什么時候結束呢.我在這里再講到一個新名詞:2MSL等待狀態,其實TIME_WAIT就是2MSL等待狀態,
為什么要設置這個狀態,原因是有足夠的時間讓ACK包到達服務器端,如果服務器端沒收到ACK包,超時了,然后重新發一個FIN包,直到服務器收到ACK 包.
TIME_WAIT狀態等待時間是在TCP重新啟動后不連接任何請求的兩倍.
大家有沒有發現一個問題:如果對方在第三次握手的時候出問題,如發FIN包的時候,不知道什么原因丟了這個包,然而這邊一直處在FIN_WAIT_2狀 態,而且TCP/IP并沒有設置這個狀態的過期時間,那他一直會保留這個狀態下去,越來越多的FIN_WAIT_2狀態會導致系統崩潰.
上面我碰到的這個問題主要因為TCP的結束流程未走完,造成連接未釋放。現設客戶端主動斷開連接,流程如下:
| Client 消息 Server |
由于Server的Socket在客戶端已經關閉時而沒有調用關閉,造成服務器端的連接處在“掛起”狀態,而客戶端則處在等待應答的狀態上。
此問題的典型特征是:一端處于FIN_WAIT2 ,而另一端處于CLOSE_WAIT。不過,根本問題還是程序寫的不好,有待提高
-------------------------------------------------------------------------
CLOSE_WAIT,TCP的癌癥,TCP的朋友。
CLOSE_WAIT狀態的生成原因
首先我們知道,如果我們的服務器程序APACHE處于CLOSE_WAIT狀態的話,說明套接字是被動關閉的!
因為如果是CLIENT端主動斷掉當前連接的話,那么雙方關閉這個TCP連接共需要四個packet:
Client ---> FIN ---> Server
Client <--- ACK <--- Server
這時候Client端處于FIN_WAIT_2狀態;而Server 程序處于CLOSE_WAIT狀態。
Client <--- FIN <--- Server
這時Server 發送FIN給Client,Server 就置為LAST_ACK狀態。
Client ---> ACK ---> Server
Client回應了ACK,那么Server 的套接字才會真正置為CLOSED狀態。
Server 程序處于CLOSE_WAIT狀態,而不是LAST_ACK狀態,說明還沒有發FIN給Client,那么可能是在關閉連接之前還有許多數據要發送或者其 他事要做,導致沒有發這個FIN packet。
通常來說,一個CLOSE_WAIT會維持至少2個小時的時間。如果有個流氓特地寫了個程序,給你造成一堆的 CLOSE_WAIT,消耗你的資源,那么通常是等不到釋放那一刻,系統就已經解決崩潰了。
只能通過修改一下TCP/IP的參數,來縮短這個時間:修改tcp_keepalive_*系列參數有助于解決這個 問題。
解決這個問題的方法是修改系統的參數,系統默認超時時間的是7200秒,也就是2小時, 這個太大了,可以修改如下幾個參數:
| sysctl?-w net.ipv4.tcp_keepalive_time=30 |
然后,執行sysctl命令使修改生效。
連接進程是通過一系列狀態表示的,這些狀態有:
LISTEN,SYN-SENT,SYN-RECEIVED,ESTABLISHED,FIN-WAIT-1,FIN-WAIT-2,CLOSE-?WAIT,CLOSING,LAST-ACK,TIME-WAIT和CLOSED
1、建立連接協議(三次握手)
- (1)?客戶 端發送一個帶SYN標志的TCP報文到服務器。這是三次握手過程中的報文1。
- (2)?服務器端回應客戶端的,這是三次握手中的第2個報文,這個報文同時帶ACK標志和SYN標 志。因此它表示對剛才客戶端SYN報文的回應;同時又標志SYN給客戶端,詢問客戶端是否準備好進行數據通 訊。
- (3)? 客戶必須再次回應服務段一個ACK報文,這是報文段3。
2、連接終止協議(四次握手)
由于TCP連 接是全雙工的,因此每個方向都必須單獨進行關閉。這原則是當一方完成它的數據發送任務后就能發送一個FIN來終 止這個方向的連接。收到一個?FIN只意味著這一方向上沒有數據流動,一個TCP連接 在收到一個FIN后仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。
- (1)?TCP客 戶端發送一個FIN,用來關閉客戶到服務器的數據傳送(報文段4)。
- (2) 服務器收到這個FIN,它發回一個ACK,確認序號為收到的序號加1(報文段5)。和SYN一 樣,一個FIN將占用一個序號。
- (3) 服務器關閉客戶端的連接,發送一個FIN給客戶端(報文段6)。
- (4) 客戶段發回ACK報文確認,并將確認序號設置為收到序號加1(報文段7)。
CLOSED: 這個沒什么好說的了,表示初始狀態。 LISTEN: 這個也是非常容易理解的一個狀態,表示服務器端的某個SOCKET處 于監聽狀態,可以接受連接了。 SYN_RCVD: 這個狀態表示接受到了SYN報 文,在正常情況下,這個狀態是服務器端的SOCKET在建立TCP連接時的三次握手會話過程中的一個中間狀態,很短暫,用netstat很難看到這種狀態的,除非你特意寫了一個客戶端測試程序,故意將三次TCP握手 過程中最后一個ACK報文不予發送。因此這種狀態時,當收到客戶端的ACK報文 后,它會進入到ESTABLISHED狀態。 SYN_SENT: 這個狀態與SYN_RCVD遙想呼應,當客戶端SOCKET執行CONNECT連接時,它首先發送SYN報文,因此也隨即它會進入到了SYN_SENT狀態,并等待服務端的發送三次握手中的第2個報文。SYN_SENT狀態表示客戶端已發送SYN報文。 ESTABLISHED:這個容易理解了,表示連接已經建立了。 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看到。 FIN_WAIT_2:上面已經詳細解釋了這種狀態,實際上FIN_WAIT_2狀態下的SOCKET,表示半連接,也即有一方要求close連接,但另外還告訴對方,我暫時還有點 數據需要傳送給你,稍后再關閉連接。 TIME_WAIT: 表示收到了對方的FIN報 文,并發送出了ACK報文,就等2MSL后即可回到CLOSED可用狀態了。如果FIN_WAIT_1狀態下收到了對方同時帶FIN標志和ACK標志的報文,則直接進入到TIME_WAIT狀態,而無須經過FIN_WAIT_2狀態。 CLOSING: 這種狀態比較特殊,實際情況中應該是很少見,屬于一種比較罕見的例外狀態。正常情況下,當你發 送FIN報文后,按理來說是應該先收到(或同時收到)對方的ACK報 文,再收到對方的FIN報文。但是CLOSING狀態表示你發送FIN報文后,并沒有收到對方的ACK報 文,反而卻也收到了對方的FIN報文。什么情況下會出現此種情況呢?其實細想一下,也不難得出結論:那就是如果雙方幾乎在同時close一 個SOCKET的話,那么就出現了雙方同時發送FIN報文的情況,也即會出現CLOSING狀態,表示雙方都正在關閉SOCKET連接。 CLOSE_WAIT: 這種狀態的含義其實是表示在等待關閉。怎么理解呢?當對方close一 個SOCKET后發送FIN報文給自己,你系統毫無疑問地會回應一個ACK報文 給對方,此時則進入到CLOSE_WAIT狀態。接下來呢,實際上你真正需要考慮的事情是察看你是否還有數據發送給對方,如果沒有的話, 那么你也就可以close這個SOCKET,發送FIN報文給對方,也即關閉連接。所以你在CLOSE_WAIT狀態下,需要完成的事情是等待你去關閉連接。 LAST_ACK: 這個狀態還是比較容易好理解的,它是被動關閉一方在發送FIN報 文后,最后等待對方的ACK報文。當收到ACK報文后,也即可以進入到CLOSED可用狀態了。
最后有2個問題 的回答,我自己分析后的結論(不一定保證100%正確)
1、?為什么建立連接協議是三次握手,而關閉連接卻是四次握手呢?
這是因為服務端的LISTEN狀態下的SOCKET當收到SYN報文的建連請求后,它可以把ACK和SYN(ACK起 應答作用,而SYN起同步作用)放在一個報文里來發送。但關閉連接時,當收到對方的FIN報文 通知時,它僅僅表示對方沒有數據發送給你了;但未必你所有的數據都全部發送給對方了,所以你可以未必會馬上會關閉SOCKET,也即你可能還需要發送一些數據給對方之后,再發送FIN報文給對方來表示你同意現在可以關閉連接了,所以它這里的ACK報文 和FIN報文多數情況下都是分開發送的。
2、?為什么TIME_WAIT狀態還需要等2MSL后才能返回到CLOSED狀 態?
這是因為:雖然雙方 都同意關閉連接了,而且握手的4個報文也都協調和發送完畢,按理可以直接回到CLOSED狀 態(就好比從SYN_SEND狀態到ESTABLISH狀態那樣);但是因為我們必須要假想網絡是不可靠的,你無法保證你最后發送的ACK報 文會一定被對方收到,因此對方處于LAST_ACK狀態下的SOCKET可能會因為超時未收到ACK報文,而重發FIN報 文,所以這個TIME_WAIT狀態的作用就是用來重發可能丟失的ACK報 文,并保證于此。
?? ??斷開連接的時候, 當發起主動關閉的左邊這方發送一個FIN過去后,
右邊被動關閉的這方要回應一個ACK,這個ACK是TCP回應的,而不是應用程序發送的,
此時,被動關閉的一方就處于CLOSE_WAIT狀態了。
如果此時被動關閉的這一方不再繼續調用closesocket,那么他就不會發送接下來的FIN,導致自己老是處于CLOSE_WAIT。
只有被動關閉的這一方調用了?closesocket,才會發送一個FIN給主動關閉的這一方,同時也使得自己的狀態變遷為LAST_ACK。?
比如被動關閉的是客戶端
當對方調用closesocket的時候,你的程序正在?
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.
服務器上出現大量的close_wait的例子和解決方法(例子從網上找的,基本差不多)
$ /usr/sbin/lsof -i | grep 6800 $ /usr/sbin/lsof -i | grep 6800 Oracle 22725 oracle9i 3u IPv4 18621468 TCP RHEL3:6800 (LISTEN) oracle 22725 oracle9i 4u IPv4 18621469 TCP RHEL3:6800->RHEL3:2174 (CLOSE_WAIT) oracle 22725 oracle9i 8u IPv4 18621568 TCP RHEL3:6800->RHEL3:2175 (CLOSE_WAIT) oracle 22725 oracle9i 9u IPv4 18621578 TCP RHEL3:6800->RHEL3:2176 (CLOSE_WAIT) oracle 22726 oracle9i 3u IPv4 18621468 TCP RHEL3:6800 (LISTEN) oracle 22726 oracle9i 4u IPv4 18621469 TCP RHEL3:6800->RHEL3:2174 (CLOSE_WAIT) oracle 22726 oracle9i 8u IPv4 18621568 TCP RHEL3:6800->RHEL3:2175 (CLOSE_WAIT) oracle 22726 oracle9i 9u IPv4 18621578 TCP RHEL3:6800->RHEL3:2176 (CLOSE_WAIT) $ kill -9 22725 # 22725, 22726就是使用該6800端口的進程號(PID)。 $ /usr/sbin/lsof -i | grep 6800進程被kill時,會釋放占用的所有鏈接句柄。
該問題的出現原因網上到處都是,也就是Socket的Client端出現異常沒有Close就退出了。
TCP連接過程是狀態的轉換,促使發生狀態轉換的是用戶調用
TCP基本知識點
上面的狀態遷移圖,基本上把TCP三次握手和四次握手的大致流程描述的非常清楚了,下面我們用文字將上面的過程描述一遍,并對異常情況進行分析:
三次握手概述:
1.? 這里需要提一下的是,服務器會維護一個半連接隊列,用于等待客戶的第三次握手請求。當收到第三次握手請求或者多次重傳失敗后,服務器會將該半連接從隊列中刪除。(這里暫且不去深究半連接隊列的等待重新策略和配置)
2.? 我們經常聽說的DDos攻擊,就可以這個環節實現,syn flood就是一種常見的DDos攻擊方式。簡單來說,syn flood就是只發送第一次握手請求后,就關閉連接,將服務器的半連接隊列占滿,從而讓正常用戶無法得到服務。
? ? ? ? RST和SIGPIPE:有過網絡編程經驗的人都知道在寫網絡通信的時候,需要屏蔽SIGPIPE信號,否則的話,一旦收到PIPE信號會導致程序異常退出。其實這個SIGPIPE就是由于write()的時候,我們自己的狀態是ESTABLISHED而對方的狀態不是ESTABLISHED,那么對方就會給我們一個RST回應,收到這個回應之后,系統就會自動生成一個PIPE信號。
四次揮手概述:
兩個WAIT:
四次握手的異常:
TCP的同時打開和同時關閉
除了上面的順序打開,和順序關閉方式,TCP還有同時打開和同時關閉的流程: 同時打開流程:(引自:http://hi.baidu.com/psorqkxcsfbbghd/item/70f3bd91943b9248f14215cd)兩個應用程序同時執行主動打開的情況是可能的,雖然發生的可能性較低。每一端都發送一個SYN,并傳遞給對方,且每一端都使用對端所知的端口作為本地端口。 例如:
主機a中一應用程序使用7777作為本地端口,并連接到主機b 8888端口做主動打開。
主機b中一應用程序使用8888作為本地端口,并連接到主機a 7777端口做主動打開。
tcp協議在遇到這種情況時,只會打開一條連接。
這個連接的建立過程需要4次數據交換,而一個典型的連接建立只需要3次交換(即3次握手)
但多數伯克利版的tcp/ip實現并不支持同時打開
同時關閉流程:(引自:http://hi.baidu.com/psorqkxcsfbbghd/item/70f3bd91943b9248f14215cd)
如果應用程序同時發送FIN,則在發送后會首先進入FIN_WAIT_1狀態。在收到對端的FIN后,回復一個ACK,會進入CLOSING狀態。在收到對端的ACK后,進入TIME_WAIT狀態。這種情況稱為同時關閉。
同時關閉也需要有4次報文交換,與典型的關閉相同。
? ? ? ?如果上面的順序流程已經非常清楚的話,那么這兩個同時打開、同時關閉的狀態圖就不難理解了…… ? ? ? ?大家可以通過這兩張圖來對應上面socket關閉流程中,“第二次握手失敗”的解釋,其實也就不難理解,為什么客戶會進入同時關閉狀態了。因為客戶在發送了FIN之后,沒有等到ACK,而是等到了服務器的FIN,自然符合同步關閉的流程。
TCP通信中服務器處理客戶端意外斷開
如果TCP連接被對方正常關閉,也就是說,對方是正確地調用了closesocket(s)或者shutdown(s)的話,那么上面的Recv或Send調用就能馬上返回,并且報錯。這是由于close socket(s)或者shutdown(s)有個正常的關閉過程,會告訴對方“TCP連接已經關閉,你不需要再發送或者接受消息了”。
但是,如果意外斷開,客戶端(3g的移動設備)并沒有正常關閉socket。雙方并未按照協議上的四次揮手去斷開連接。
那么這時候正在執行Recv或Send操作的一方就會因為沒有任何連接中斷的通知而一直等待下去,也就是會被長時間卡住。
像這種如果一方已經關閉或異常終止連接,而另一方卻不知道,我們將這樣的TCP連接稱為半打開 的。
解決意外中斷辦法都是利用保活機制。而保活機制分又可以讓底層實現也可自己實現。
1、 自己編寫心跳包程序
簡單的說也就是在自己的程序中加入一條線程,定時向對端發送數據包,查看是否有ACK,如果有則連接正常,沒有的話則連接斷開
2、 啟動TCP編程里的keepAlive機制
一)雙方擬定心跳(自實現)
一般由客戶端發送心跳包,服務端并不回應心跳,只是定時輪詢判斷一下與上次的時間間隔是否超時(超時時間自己設定)。服務器并不主動發送是不想增添服務器的通信量,減少壓力。
但這會出現三種情況:
情況1.
客戶端由于某種網絡延遲等原因很久后才發送心跳(它并沒有斷),這時服務器若利用自身設定的超時判斷其已經斷開,而后去關閉socket。若客戶端有重連機制,則客戶端會重新連接。若不確定這種方式是否關閉了原本正常的客戶端,則在ShutDown的時候一定要選擇send,表示關閉發送通道,服務器還可以接收一下,萬一客戶端正在發送比較重要的數據呢,是不?
情況2.
客戶端很久沒傳心跳,確實是自身斷掉了。在其重啟之前,服務端已經判斷出其超時,并主動close,則四次揮手成功交互。
情況3.
客戶端很久沒傳心跳,確實是自身斷掉了。在其重啟之前,服務端的輪詢還未判斷出其超時,在未主動close的時候該客戶端已經重新連接。
這時候若客戶端斷開的時候發送了FIN包,則服務端將會處于CLOSE_WAIT狀態;
這時候若客戶端斷開的時候未發送FIN包,則服務端處還是顯示ESTABLISHED狀態;
而新連接上來的客戶端(也就是剛才斷掉的重新連上來了)在服務端肯定是ESTABLISHED;這時候就有個問題,若利用輪詢還未檢測出上條舊連接已經超時(這很正常,timer總有個間隔吧),而在這時,客戶端又重復的上演情況3,那么服務端將會出現大量的假的ESTABLISHED連接和CLOSE_WAIT連接。
最終結果就是新的其他客戶端無法連接上來,但是利用netstat還是能看到一條連接已經建立,并顯示ESTABLISHED,但始終無法進入程序代碼。個人最初感覺導致這種情況是因為假的ESTABLISHED連接和 CLOSE_WAIT連接會占用較大的系統資源,程序無法再次創建連接(因為每次我發現這個問題的時候我只連了10個左右客戶端卻已經有40多條無效連接)。而最近幾天測試卻發現有一次程序內只連接了2,3個設備,但是有8條左右的虛連接,此時已經連接不了新客戶端了。這時候我就覺得我想錯了,不可能這幾條連接就占用了大量連接把,如果說幾十條還有可能。但是能肯定的是,這個問題的產生絕對是設備在不停的重啟,而服務器這邊又是簡單的輪詢,并不能及時處理,暫時還未能解決。
二)利用KeepAlive
其實keepalive的原理就是TCP內嵌的一個心跳包,
以服務器端為例,如果當前 server 端檢測到超過一定時間(默認是 7,200,000 milliseconds ,也就是 2 個小時)沒有數據傳輸,那么會向 client 端發送一個 keep-alive packet (該 keep-alive packet 就是 ACK和 當前 TCP 序列號減一的組合),此時 client 端應該為以下三種情況之一:
1. client 端仍然存在,網絡連接狀況良好。此時 client 端會返回一個 ACK 。server 端接收到 ACK 后重置計時器(復位存活定時器),在 2 小時后再發送探測。如果 2 小時內連接上有數據傳輸,那么在該時間基礎上向后推延 2 個小時。
2. 客戶端異常關閉,或是網絡斷開。在這兩種情況下, client 端都不會響應。服務器沒有收到對其發出探測的響應,并且在一定時間(系統默認為 1000 ms )后重復發送 keep-alive packet ,并且重復發送一定次數( 2000 XP 2003 系統默認為 5 次 , Vista 后的系統默認為 10 次)。
3. 客戶端曾經崩潰,但已經重啟。這種情況下,服務器將會收到對其存活探測的響應,但該響應是一個復位,從而引起服務器對連接的終止。對于應用程序來說,2小時的空閑時間太長。因此,我們需要手工開啟Keepalive功能并設置合理的Keepalive參數。
全局設置可更改 /etc/sysctl.conf ,加上:
net.ipv4.tcp_keepalive_intvl = 20
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_time = 60
在程序中設置如下:
wireshark 抓包圖解 TCP 三次握手四次斷開
一. TCP/IP協議族
? ? ? TCP/IP是一個協議族,通常分不同層次進行開發,每個層次負責不同的通信功能。包含以下四個層次:
1. 鏈路層,也稱作數據鏈路層或者網絡接口層,通常包括操作系統中的設備驅動程序和計算機中對應的網絡接口卡。它們一起處理與電纜(或其他任何傳輸媒介)的物理接口細節。
2. 網絡層,也稱作互聯網層,處理分組在網絡中的活動,例如分組的選路。網絡層協議包括IP協議(網際協議)、ICMP協議(Internet互聯網控制報文協議),以及IGMP協議(Internet組管理協議)。
3. 傳輸層。主要為兩臺主機上的應用程序提供端到端的通信。在TCP/IP協議族中,有兩個互不相同的傳輸協議:TCP(傳輸控制協議)和UDP(用戶數據報協 議)。TCP為兩臺主機提供高可靠性的數據通信。他所作的工作包括把應用程序交給它的數據分成合適的小塊交給下面的網絡層,確認接收到的分組,設置發送最 后確認分組的超時時鐘等。由于運輸層提供了高可靠性的端到端通信,因此應用層可以忽略所有這些細節。而另一方面,UDP則為應用層提供一種非常簡單的服 務。它只是把稱作數據報的分組從一臺主機發送到另一臺主機,但并不保證該數據報能到達另一端。任何必須的可靠性必須由應用層來提供。
4. 應用層。負責處理特定的應用程序細節。包括Telnet(遠程登錄)、FTP(文件傳輸協議)、SMTP(簡單郵件傳送協議)以及SNMP(簡單網絡管理協議)等。
wireshark抓到的包與對應的協議層如下圖所示:
- 1. Frame:? ?物理層的數據幀概況
- 2. Ethernet II: 數據鏈路層以太網幀頭部信息
- 3. Internet Protocol Version 4: 互聯網層IP包頭部信息
- 4. Transmission Control Protocol:? 傳輸層的數據段頭部信息,此處是TCP
- 5. Hypertext Transfer Protocol:? 應用層的信息,此處是HTTP協議
二. TCP協議
? ? ? TCP是一種面向連接(連接導向)的、可靠的基于字節流的傳輸層通信協議。TCP將用戶數據打包成報文段,它發送后啟動一個定時器,另一端收到的數據進行確認、對失序的數據重新排序、丟棄重復數據。
? ? ? TCP的特點有:
1. TCP是面向連接的運輸層協議
2. 每一條TCP連接只能有兩個端點,每一條TCP連接只能是點對點的
3. TCP提供可靠交付的服務
4. TCP提供全雙工通信。數據在兩個方向上獨立的進行傳輸。因此,連接的每一端必須保持每個方向上的傳輸數據序號。
5. 面向字節流。面向字節流的含義:雖然應用程序和TCP交互是一次一個數據塊,但TCP把應用程序交下來的數據僅僅是一連串的無結構的字節流
? ? ? TCP報文首部,如下圖所示:
- 1. 源端口號:數據發起者的端口號,16bit
- 2. 目的端口號:數據接收者的端口號,16bit
- 3. 序號:32bit的序列號,由發送方使用
- 4. 確認序號:32bit的確認號,是接收數據方期望收到發送方的下一個報文段的序號,因此確認序號應當是上次已成功收到數據字節序號加1。
- 5. 首部長度:首部中32bit字的數目,可表示15*32bit=60字節的首部。一般首部長度為20字節。
- 6. 保留:6bit, 均為0
- 7. 緊急URG:當URG=1時,表示報文段中有緊急數據,應盡快傳送。
- 8. 確認比特ACK:ACK = 1時代表這是一個確認的TCP包,取值0則不是確認包。
- 9. 推送比特PSH:當發送端PSH=1時,接收端盡快的交付給應用進程。
- 10. 復位比特(RST):當RST=1時,表明TCP連接中出現嚴重差錯,必須釋放連接,再重新建立連接。
- 11. 同步比特SYN:在建立連接是用來同步序號。SYN=1, ACK=0表示一個連接請求報文段。SYN=1,ACK=1表示同意建立連接。
- 12. 終止比特FIN:FIN=1時,表明此報文段的發送端的數據已經發送完畢,并要求釋放傳輸連接。
- 13. 窗口:用來控制對方發送的數據量,通知發放已確定的發送窗口上限。
- 14. 檢驗和:該字段檢驗的范圍包括首部和數據這兩部分。由發端計算和存儲,并由收端進行驗證。
- 15. 緊急指針:緊急指針在URG=1時才有效,它指出本報文段中的緊急數據的字節數。
- 16. 選項:長度可變,最長可達40字節
三. TCP三次握手
? ? ? ?TCP建立連接時,會有三次握手過程,如下圖所示,wireshark截獲到了三次握手的三個數據包。第四個包才是http的,說明http的確是使用TCP建立連接的。
?
下面來逐步分析三次握手過程:
第一次握手:客戶端向服務器發送連接請求包,標志位SYN(同步序號)置為1,序號為X=0
?
第 二次握手:服務器收到客戶端發過來報文,由SYN=1知道客戶端要求建立聯機。向客戶端發送一個SYN和ACK都置為1的TCP報文,設置初始序號 Y=0,將確認序號(Acknowledgement Number)設置為客戶的序列號加1,即X+1 = 0+1=1, 如下圖:
?
第 三次握手:客戶端收到服務器發來的包后檢查確認序號(Acknowledgement Number)是否正確,即第一次發送的序號加1(X+1=1)。以及標志位ACK是否為1。若正確,服務器再次發送確認包,ACK標志位為1,SYN標 志位為0。確認序號(Acknowledgement Number)=Y+1=0+1=1,發送序號為X+1=1。客戶端收到后確認序號值與ACK=1則連接建立成功,可以傳送數據了。
四. TCP四次揮手
? ? ? ?TCP斷開連接時,會有四次揮手過程,如下圖所示,wireshark截獲到了四次揮手的四個數據包。
?
下面來逐步分析四次揮手過程:
第一次揮手:客戶端給服務器發送TCP包,用來關閉客戶端到服務器的數據傳送。將標志位FIN和ACK置為1,序號為X=1,確認序號為Z=1。
服務器收到FIN后,發回一個ACK(標志位ACK=1),確認序號為收到的序號加1,即X=X+1=2。序號為收到的確認序號=Z。
?
服務器關閉與客戶端的連接,發送一個FIN。標志位FIN和ACK置為1,序號為Y=1,確認序號為X=2。
?
客戶端收到服務器發送的FIN之后,發回ACK確認(標志位ACK=1),確認序號為收到的序號加1,即Y+1=2。序號為收到的確認序號X=2。
?
總結
- 上一篇: Metasploit
- 下一篇: tcpdump - 数据包进行截获的包分