1. 程式人生 > >TCP 握手和揮手圖解(有限狀態機)

TCP 握手和揮手圖解(有限狀態機)

一個 tle 客戶端使用 正常的 一點 而不是 情況下 tcp狀態機 流程

1、引言

TCP 這段看過好幾遍,老是記不住,沒辦法找工作涉及到網絡編程這塊,各種問 TCP 。今天好好整理一下握手和揮手過程。獻給跟我一樣忙碌,找工作的童鞋,歡迎大神批評指正。

2、TCP 的連接建立

技術分享圖片

上圖畫出了 TCP 建立連接的過程。假定主機 A 是 TCP 客戶端,B是服務端。最初兩端的 TCP 進程都處於 CLOSED 狀態。圖中在主機下面的是 TCP進程所處的狀態。A 是主動打開連接,B 是被動打開連接。

三次握手過程分析:
(1)首先A向B發出連接請求報文段,這時首部中的同步位SYN=1,同時選擇一個初始序號 seq=x。TCP規定,SYN報文段不能攜帶數據,但要消耗掉一個序號。這時,A進入SYN-SENT狀態。【備註:序號指的是 TCP 報文段首部20字節裏的序號,TCP 連接傳送的字節流的每一個字節都按順序編號,具體可以看看 TCP 可靠傳輸實現的原理】
(2)B收到請求後,向A發送確認。在確認報文段中把SYN和ACK位都置為1,確認號是ack=x+1,同時也為自己選擇一個初始序號seq=y。請註意,這個報文段也不能攜帶數據,但同樣要消耗掉一個序號。這時B進入SYN-RCVD狀態。
(3)A收到B的確認後,還要向B給出確認。確認報文段的ACK置為1,確認號ack=y+1,而自己的序號seq=x+1。這時,TCP連接已經建立,A進入ESTABLISHED 狀態,當B收到A的確認後,也會進入 ESTABLISHED 狀態。

以上給出的連接建立過程就是常說的TCP三次握手。

2.1 為什麽需要三次握手過程(面試經常問)

為什麽A還要發送一次確認呢?這主要是為了防止已失效的連接請求報文段突然又傳送到了B,因而產生錯誤。
  所謂已失效的連接請求報文段是這樣產生的。A發送連接請求,但因連接請求報文丟失而未收到確認,於是A重發一次連接請求,成功後建立了連接。數據傳輸完畢後就釋放了連接。現在假定A發出的第一個請求報文段並未丟失,而是在某個網絡節點長時間滯留了,以致延誤到連接釋放以後的某個時間才到達B。本來這是一個早已失效的報文段。但B收到此失效的連接請求報文段後,就誤以為A又發了一次新的連接請求,於是向A發出確認報文段,同意建立連接。假如不采用三次握手,那麽只要B發出確認,新的連接就建立了。
  由於A並未發出建立連接的請求,因此不會理睬B的確認,也不會向B發送數據。但B卻以為新的運輸連接已經建立了,並一直等待A發來數據,因此白白浪費了許多資源。
  采用TCP三次握手的方法可以防止上述現象發生。例如在剛才的情況下,由於A不會向B的確認發出確認,連接就不會建立。

2.2 如果在TCP第三次握手中的報文段丟失了會發生什麽情況?

Client認為這個連接已經建立,如果Client端向Server寫數據,Server端將以RST包響應,方能感知到Server的錯誤。

3、TCP 連接釋放

技術分享圖片

四次握手(兩個二次握手)過程分析:

數據傳輸結束後,通信的雙方都可以釋放連接,並停止發送數據。假設現在客戶端和服務端都處於ESTABLISHED狀態。

(1)客戶端 A 的 TCP 進程先向服務端發出連接釋放報文段,並停止發送數據,主動關閉 TCP 連接。釋放連接報文段中 FIN=1,序號為 seq=u,該序號等於前面已經傳送過去的數據的最後一個字節的序號加1。這時,A進入 FIN—WAIT-1 (終止等待1)狀態,等待 B 的確認。TCP 規定,FIN報文段即使不攜帶數據,也要消耗掉一個序號。這是 TCP 連接釋放的第一次揮手。
(2)B收到連接釋放報文段後即發出確認釋放連接的報文段,該報文段中,ACK=1,確認號為ack=u+1,其自己的序號為v,該序號等於B前面已經傳送過的數據的最後一個字節的序號加1。然後B進入CLOSE—WAIT(關閉等待)狀態,此時TCP服務器進程應該通知上層的應用進程,因而A到B這個方向的連接就釋放了,這時TCP處於半關閉狀態,即A已經沒有數據要發了,但B若發送數據,A仍要接受,也就是說從B到A這個方向的連接並沒有關閉,這個狀態可能會持續一些時間。這是TCP連接釋放的第二次揮手。

(3)A收到B的確認後,就進入了FIN—WAIT(終止等待2)狀態,等待B發出連接釋放報文段,如果B已經沒有要向A發送的數據了,其應用進程就通知TCP釋放連接。這時B發出的鏈接釋放報文段中,FIN=1,確認號還必須重復上次已發送過的確認號,即ack=u+1,序號seq=w,因為在半關閉狀態B可能又發送了一些數據,因此該序號為半關閉狀態發送的數據的最後一個字節的序號加1。這時B進入LAST—ACK(最後確認)狀態,等待A的確認,這是TCP連接的第三次揮手。

(4)A收到B的連接釋放請求後,必須對此發出確認。確認報文段中,ACK=1,確認號ack=w+1,而自己的序號seq=u+1,而後進入TIME—WAIT(時間等待)狀態。這時候,TCP連接還沒有釋放掉,必須經過時間等待計時器設置的時間2MSL後,A才進入CLOSED狀態,時間MSL叫做最長報文壽命,RFC建議設為2分鐘,因此從A進入TIME—WAIT狀態後,要經過4分鐘才能進入到CLOSED狀態,而B只要收到了A的確認後,就進入了CLOSED狀態。二者都進入CLOSED狀態後,連接就完全釋放了,這是TCP連接的第四次揮手。

3.1 為什麽需要四次揮手
①、為了保證A發送的最後一個ACK報文段能夠到達B。即最後這個確認報文段很有可能丟失,那麽B會超時重傳,然後A再一次確認,同時啟動2MSL計時器,如此下去。如果沒有等待時間,發送完確認報文段就立即釋放連接的話,B就無法重傳了(連接已被釋放,任何數據都不能出傳了),因而也就收不到確認,就無法按照步驟進入CLOSE狀態,即必須收到確認才能close。
②、防止“已失效的連接請求報文段”出現在連接中。經過2MSL,那些在這個連接持續的時間內,產生的所有報文段就可以都從網絡中消失。即在這個連接釋放的過程中會有一些無效的報文段滯留在樓閣結點,但是呢,經過2MSL這些無效報文段就肯定可以發送到目的地,不會滯留在網絡中。這樣的話,在下一個連接中就不會出現上一個連接遺留下來的請求報文段了。

可以看出:B結束TCP連接的時間比A早一點,因為B收到確認就斷開連接了,而A還得等待2MSL.

【備註】當客戶端執行主動關閉並進入TIME—WAIT是正常的,服務端執行被動關閉,不會進入TIME—WAIT狀態,這說明,如果終止了一個客戶程序,並立即重啟該客戶程序,則新的客戶程序將不再重用相同的本地端口,而是使用新的端口,這不會帶來什麽問題,因為客戶端使用本地端口,而並不關心這個端口是多少。但對於服務器來說,情況就不同了,服務器總是用我們熟知的端口,那麽在2MSL時間內,重啟服務器就會出錯,為了避免這個錯誤,服務器給出了一個平靜時間的概念,這是說在2MSL時間內,雖然可以重新啟動服務器,但是這個服務器還是要平靜的等待2MSL時間的過去才能進行下一次連接。

4、有限狀態機

技術分享圖片

上圖是 TCP 的狀態機,看了很久,感覺還是理解得不深。先在這裏打個點。

(1)CLOSED 狀態時初始狀態。

(2)LISTEN:被動打開,服務器端的 狀態變為LISTEN(監聽)。被動打開的概念:連接的一端的應用程序通知操作系統,希望建立一個傳入的連接。這時候操作系統為連接的這一端建立一個連 接。與之對應的是主動連接:應用程序通過主動打開請求來告訴操作系統建立一個連接。

(3)SYNRECVD:服務器端收到SYN後,狀態為SYN;發送SYN ACK;

(4)SYN_SENTY:應用程序發送SYN後,狀態為SYN_SENT;

(5)ESTABLISHED:SYNRECVD收到ACK後,狀態為ESTABLISHED; SYN_SENT在收到SYN ACK,發送ACK,狀態為ESTABLISHED;

(6)CLOSE_WAIT:服務器端在收到FIN後,發送ACK,狀態為CLOSE_WAIT;如果此時服務器端還有數據需要發送,那麽就發送,直到數據發送完畢;此時,服務器端發送FIN,狀態變為LAST_ACK;

(7)FIN_WAIT_1:應用程序端發送FIN,準備斷開TCP連接;狀態從ESTABLISHED——>FIN_WAIT_1;

(8)FIN_WAIT_2:應用程序端只收到服務器端得ACK信號,並沒有收到FIN信號;說明服務器端還有數據傳輸,那麽此時為半連接;

(9)TIME_WAIT:有兩種方式進入 該狀態:1、FIN_WAIT_1進入:此時應用程序端口收到FIN+ACK(而不是像FIN_WAIT_2那樣只收到ACK,說明數據已經發送完畢)並 向服務器端口發送ACK;2、FIN_WAIT_2進入:此時應用程序端口收到了FIN,然後向服務器端發送ACK;TIME_WAIT是為了實現TCP 全雙工連接的可靠性關閉,用來重發可能丟失的ACK報文;需要持續2個MSL(最大報文生存時間):假設應用程序端口在進入TIME_WAIT後,2個 MSL時間內並沒有收到FIN,說明應用程序最後發出的ACK已經收到了;否則,會在2個MSL內在此收到ACK報文;

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

4.2.服務器的狀態遷移圖
服務器的狀態可以用如下的流程來表示:
CLOSED->LISTEN->SYN收到->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED
在建立連接的時候,服務器端是在第三次握手之後才進入數據交互狀態,而關閉連接則是在關閉連接的第二次握手以後(註意不是第四次)。而關閉以後還要等待客戶端給出最後的ACK包才能進入初始的狀態。

4.3.其他狀態遷移
書中的圖還有一些其他的狀態遷移,這些狀態遷移針對服務器和客戶端兩方面的總結如下
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狀態並等待關閉。

4.4.2MSL等待狀態
書中給的圖裏面,有一個TIME_WAIT等待狀態,這個狀態又叫做2MSL狀態,說的是在TIME_WAIT2發送了最後一個ACK數據報以後,要進入TIME_WAIT狀態,這個狀態是防止最後一次握手的數據報沒有傳送到對方那裏而準備的(註意這不是四次握手,這是第四次握手的保險狀態)。這個狀態在很大程度上保證了雙方都可以正常結束,但是,問題也來了。

由於插口的2MSL狀態(插口是IP和端口對的意思,socket),使得應用程序在2MSL時間內是無法再次使用同一個插口的,對於客戶程序還好一些,但是對於服務程序,例如httpd,它總是要使用同一個端口來進行服務,而在2MSL時間內,啟動httpd就會出現錯誤(插口被使用)。為了避免這個錯誤,服務器給出了一個平靜時間的概念,這是說在2MSL時間內,雖然可以重新啟動服務器,但是這個服務器還是要平靜的等待2MSL時間的過去才能進行下一次連接。

4.5.FIN_WAIT_2狀態
這就是著名的半關閉的狀態了,這是在關閉連接時,客戶端和服務器兩次握手之後的狀態。在這個狀態下,應用程序還有接受數據的能力,但是已經無法發送數據,但是也有一種可能是,客戶端一直處於FIN_WAIT_2狀態,而服務器則一直處於WAIT_CLOSE狀態,而直到應用層來決定關閉這個狀態。
5.RST,同時打開和同時關閉
RST是另一種關閉連接的方式,應用程序應該可以判斷RST包的真實性,即是否為異常中止。而同時打開和同時關閉則是兩種特殊的TCP狀態,發生的概率很小。

5、總結

本文詳細分析了 TCP 握手和揮手的過程,簡單給TCP狀態機各個狀態註釋一下。碰到新的問題再補充。

TCP 握手和揮手圖解(有限狀態機)