文章出處


WEB產品的性能測試,有很多tcp連接方面的問題,也因為這方面的問題,導致性能出現不穩定等情況,客戶端和服務器之間數據傳輸,以及之間連接狀態的轉變,哪些狀態是正常的狀態,哪些狀態是異常的狀態,怎樣去定位這些問題,以及常用的工具,今天針對這些問題簡單的總結了一下;

 

1

TCP狀態獲取

    1)netstat -nat  查看TCP各個狀態的數量

    2)lsof  -i:port  可以檢測到打開套接字的狀況

    3)  sar -n SOCK 查看tcp創建的連接數

    4)  tcpdump -iany tcp port 9000 對tcp端口為9000的進行抓包

 

 

 

2

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. 在發送連接請求后等待匹配的連接請求

  當請求連接時客戶端首先要發送同步信號給要訪問的機器,此時狀態為SYN_SENT,如果連接成功了就變為ESTABLISHED,正常情況下SYN_SENT狀態非常短暫。例如要訪問網站http://www.baidu.com,如果是正常連接的話,用TCPView觀察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(拒絕服務攻擊)攻擊了。

 

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的連接中斷請求,或先前的連接中斷請求的確認 */

  如果服務器出現shutdown再重啟,使用netstat -nat查看,就會看到很多FIN-WAIT-1的狀態。就是因為服務器當前有很多客戶端連接,直接關閉服務器后,無法接收到客戶端的ACK。

FIN-WAIT-2:從遠程TCP等待連接中斷請求

  主動關閉端接到ACK后,就進入了FIN-WAIT-2,從遠程TCP等待連接中斷請求

  這就是著名的半關閉的狀態了,這是在關閉連接時,客戶端和服務器兩次握手之后的狀態。在這個狀態下,應用程序還有接受數據的能力,但是已經無法發送數據,但是也有一種可能是,客戶端一直處于FIN_WAIT_2狀態,而服務器則一直處于WAIT_CLOSE狀態,而直到應用層來決定關閉這個狀態。

CLOSE-WAIT:等待從本地用戶發來的連接中斷請求

  被動關閉(passive close)端TCP接到FIN后,就發出ACK以回應FIN請求(它的接收也作為文件結束符傳遞給上層應用程序),并進入CLOSE_WAIT.等待從本地用戶發來的連接中斷請求

     

CLOSING:等待遠程TCP對連接中斷的確認

等待遠程TCP對連接中斷的確認

LAST-ACK:等待原來的發向遠程TCP的連接中斷請求的確認

被動關閉端一段時間后,接收到文件結束符的應用程序將調用CLOSE關閉連接。這導致它的TCP也發送一個 FIN,等待對方的ACK.就進入了LAST-ACK 等待原來發向遠程TCP的連接中斷請求的確認

使用并發壓力測試的時候,突然斷開壓力測試客戶端,服務器會看到很多LAST-ACK。

TIME-WAIT:等待足夠的時間以確保遠程TCP接收到連接中斷請求的確認

在主動關閉端接收到FIN后,TCP就發送ACK包,并進入TIME-WAIT狀態,等待足夠的時間以確保遠程TCP接收到連接中斷請求的確認 */

      TIME_WAIT等待狀態,這個狀態又叫做2MSL狀態,說的是在TIME_WAIT2發送了最后一個ACK數據報以后,要進入TIME_WAIT狀態,這個狀態是防止最后一次握手的數據報沒有傳送到對方那里而準備的(注意這不是四次握手,這是第四次握手的保險狀態)。這個狀態在很大程度上保證了雙方都可以正常結束,但是,問題也來了。由于插口的2MSL狀態(插口是IP和端口對的意思,socket),使得應用程序在2MSL時間內是無法再次使用同一個插口的,對于客戶程序還好一些,但是對于服務程序,例如httpd,它總是要使用同一個端口來進行服務,而在2MSL時間內,啟動httpd就會出現錯誤(插口被使用)。為了避免這個錯誤,服務器給出了一個平靜時間的概念,這是說在2MSL時間內,雖然可以重新啟動服務器,但是這個服務器還是要平靜的等待2MSL時間的過去才能進行下一次連接。

TCP的狀態遷移圖看似復雜,但是仔細觀察,是有兩條線路的;

客戶端應用程序的狀態遷移圖

客戶端的狀態可以用如下的流程來表示:

       CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED

以上流程是在程序正常的情況下應該有的流程,從書中的圖中可以看到,在建立連接時,當客戶端收到SYN報文的ACK以后,客戶端就打開了數據交互地連接。而結束連接則通常是客戶端主動結束的,客戶端結束應用程序以后,需要經歷FIN_WAIT_1,FIN_WAIT_2等狀態,這些狀態的遷移就是前面提到的結束連接的四次握手。

服務器的狀態遷移圖

服務器的狀態可以用如下的流程來表示:

        CLOSED->LISTEN->SYN收到->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED

在建立連接的時候,服務器端是在第三次握手之后才進入數據交互狀態,而關閉連接則是在關閉連接的第二次握手以后(注意不是第四次)。而關閉以后還要等待客戶端給出最后的ACK包才能進入初始的狀態。

其他狀態遷移

還有一些其他的狀態遷移,這些狀態遷移針對服務器和客戶端兩方面的總結如下

LISTEN->SYN_SENT,對于這個解釋就很簡單了,服務器有時候也要打開連接的嘛。

SYN_SENT->SYN收到,服務器和客戶端在SYN_SENT狀態下如果收到SYN數據報,則都需要發送SYN的ACK數據報并把自己的狀態調整到SYN收到狀態,準備進入ESTABLISHED

SYN_SENT->CLOSED,在發送超時的情況下,會返回到CLOSED狀態。

SYN_收到->LISTEN,如果受到RST包,會返回到LISTEN狀態。

SYN_收到->FIN_WAIT_1,這個遷移是說,可以不用到ESTABLISHED狀態,而可以直接跳轉到FIN_WAIT_1狀態并等待關閉。

 

 

 

 

3

TCP連接建立三次握手

Client連接Server:

當Client端調用socket函數調用時,相當于Client端產生了一個處于Closed狀態的套接字。

  1)第一次握手:Client端又調用connect函數調用,系統為Client隨機分配一個端口,連同傳入connect中的參數(Server的IP和端口),這就形成了一個連接四元組,客戶端發送一個帶SYN標志的TCP報文到服務器。這是三次握手過程中的報文1。connect調用讓Client端的socket處于SYN_SENT狀態,等待服務器確認;SYN:同步序列編號(Synchronize Sequence Numbers)。

  2)第二次握手: 服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;

  3)第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶器和客務器進入ESTABLISHED狀態,完成三次握手。連接已經可以進行讀寫操作。

一個完整的三次握手也就是: 請求---應答---再次確認。

TCP協議通過三個報文段完成連接的建立,這個過程稱為三次握手(three-way handshake),過程如下圖所示。

對應的函數接口:

Server:

  當Server端調用socket函數調用時,相當于Server端產生了一個處于Closed狀態的監聽套接字

  Server端調用bind操作,將監聽套接字與指定的地址和端口關聯,然后又調用listen函數,系統會為其分配未完成隊列和完成隊列,此時的監聽套接字可以接受Client的連接,監聽套接字狀態處于LISTEN狀態。

  當Server端調用accept操作時,會從完成隊列中取出一個已經完成的client連接,同時在server這段會產生一個會話套接字,用于和client端套接字的通信,這個會話套接字的狀態是ESTABLISH。

從圖中可以看出,當客戶端調用connect時,觸發了連接請求,向服務器發送了SYN J包,這時connect進入阻塞狀態;服務器監聽到連接請求,即收到SYN J包,調用accept函數接收請求向客戶端發送SYN K ,ACK J+1,這時accept進入阻塞狀態;客戶端收到服務器的SYN K ,ACK J+1之后,這時connect返回,并對SYN K進行確認;服務器收到ACK K+1時,accept返回,至此三次握手完畢,連接建立。

我們可以通過網絡抓包的查看具體的流程:

比如我們服務器開啟9502的端口。使用tcpdump來抓包:

tcpdump -iany tcp port 9502

然后我們使用telnet 127.0.0.1 9502開連接.:

telnet 127.0.0.1 9502

14:12:45.104687 IP localhost.39870 > localhost.9502: Flags [S], seq 2927179378, win 32792, options [mss 16396,sackOK,TS val 255474104 ecr 0,nop,wscale 3], length 0(1)

14:12:45.104701 IP localhost.9502 > localhost.39870: Flags [S.], seq 1721825043, ack 2927179379, win 32768, options [mss 16396,sackOK,TS val 255474104 ecr 255474104,nop,wscale 3], length 0  (2)

14:12:45.104711 IP localhost.39870 > localhost.9502: Flags [.], ack 1, win 4099, options [nop,nop,TS val 255474104 ecr 255474104], length 0  (3)

14:13:01.415407 IP localhost.39870 > localhost.9502: Flags [P.], seq 1:8, ack 1, win 4099, options [nop,nop,TS val 255478182 ecr 255474104], length 7

14:13:01.415432 IP localhost.9502 > localhost.39870: Flags [.], ack 8, win 4096, options [nop,nop,TS val 255478182 ecr 255478182], length 0

14:13:01.415747 IP localhost.9502 > localhost.39870: Flags [P.], seq 1:19, ack 8, win 4096, options [nop,nop,TS val 255478182 ecr 255478182], length 18

14:13:01.415757 IP localhost.39870 > localhost.9502: Flags [.], ack 19, win 4097, options [nop,nop,TS val 255478182 ecr 255478182], length 0

我們看到 (1)(2)(3)三步是建立tcp:

第一次握手:

14:12:45.104687 IP localhost.39870 > localhost.9502: Flags [S], seq 2927179378

客戶端IP localhost.39870 (客戶端的端口一般是自動分配的) 向服務器localhost.9502 發送syn包(syn=j)到服務器》

syn的seq= 2927179378

第二次握手:

14:12:45.104701 IP localhost.9502 > localhost.39870: Flags [S.], seq 1721825043, ack 2927179379,

服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包

SYN(ack=j+1)=ack 2927179379    服務器主機SYN包(syn=seq 1721825043)

第三次握手:

14:12:45.104711 IP localhost.39870 > localhost.9502: Flags [.], ack 1,

客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1)

客戶端和服務器進入ESTABLISHED狀態后,可以進行通信數據交互。此時和accept接口沒有關系,即使沒有accepte,也進行3次握手完成。

連接出現連接不上的問題,一般是網路出現問題或者網卡超負荷或者是連接數已經滿啦。

紫色背景的部分:

IP localhost.39870 > localhost.9502: Flags [P.], seq 1:8, ack 1, win 4099, options [nop,nop,TS val 255478182 ecr 255474104], length 7

客戶端向服務器發送長度為7個字節的數據,

IP localhost.9502 > localhost.39870: Flags [.], ack 8, win 4096, options [nop,nop,TS val 255478182 ecr 255478182], length 0

服務器向客戶確認已經收到數據

IP localhost.9502 > localhost.39870: Flags [P.], seq 1:19, ack 8, win 4096, options [nop,nop,TS val 255478182 ecr 255478182], length 18

然后服務器同時向客戶端寫入數據。

IP localhost.39870 > localhost.9502: Flags [.], ack 19, win 4097, options [nop,nop,TS val 255478182 ecr 255478182], length 0

客戶端向服務器確認已經收到數據

這個就是tcp可靠的連接,每次通信都需要對方來確認。

 

 

 

4


TCP連接終止(四次握手)

 

由于TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這原則是當一方完成它的數據發送任務后就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味著這一方向上沒有數據流動,一個TCP連接在收到一個FIN后仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。

     建立一個連接需要三次握手,而終止一個連接要經過四次握手,這是由TCP的半關閉(half-close)造成的,如圖:

(1)客戶端A發送一個FIN,用來關閉客戶A到服務器B的數據傳送(報文段4)。

(2)服務器B收到這個FIN,它發回一個ACK,確認序號為收到的序號加1(報文段5)。和SYN一樣,一個FIN將占用一個序號。

(3)服務器B關閉與客戶端A的連接,發送一個FIN給客戶端A(報文段6)。

(4)客戶端A發回ACK報文確認,并將確認序號設置為收到序號加1(報文段7)。

對應函數接口如圖:

調用過程如下:

  • 1) 當client想要關閉它與server之間的連接。client(某個應用進程)首先調用close主動關閉連接,這時TCP發送一個FIN M;client端處于FIN_WAIT1狀態。

  • 2) 當server端接收到FIN M之后,執行被動關閉。對這個FIN進行確認,返回給client ACK。當server端返回給client ACK后,client處于FIN_WAIT2狀態,server處于CLOSE_WAIT狀態。它的接收也作為文件結束符傳遞給應用進程,因為FIN的接收 意味著應用進程在相應的連接上再也接收不到額外數據;

  • 3) 一段時間之后,當server端檢測到client端的關閉操作(read返回為0)。接收到文件結束符的server端調用close關閉它的socket。這導致server端的TCP也發送一個FIN N;此時server的狀態為LAST_ACK。

  • 4) 當client收到來自server的FIN后 。 client端的套接字處于TIME_WAIT狀態,它會向server端再發送一個ack確認,此時server端收到ack確認后,此套接字處于CLOSED狀態。

 

 

 

安大叔說

人只有保持不甘心的心態才能激勵自己不斷努力前進,所以永遠保持自己的不甘吧,去學習去讓自己進步,而不是認命和抱怨

 

 

 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()