【網路協議】TCP連線的建立和釋放

蘭亭風雨發表於2014-06-09

轉載請註明出處:http://blog.csdn.net/ns_code/article/details/29382883


TCP首部格式

    先看TCP報文段的格式,如下;


    TCP報文段首部的前20個位元組是固定的,後面有4N位元組是根據需要而增加的選項。因此TCP報文段的最小長度為20個位元組。

    首部固定部分的各欄位的意義如下:

    1、源埠和目的埠:加上IP首部的源IP地址和目的IP地址,確定唯一的一個TCP連線。另外通過目的埠來決定TCP將資料包交付於那個應用程式,從而實現TCP的分用功能。

    2、序號:佔4個位元組,序號的範圍為[0,4284967296]。由於TCP是面向位元組流的,在一個TCP連線中傳送的位元組流中的每一個位元組都按順序編號,首部中的序號欄位則是指本報文段所傳送的資料的第一個位元組的序號。另外,序號是迴圈使用的,當序號增加到最大值時,下一個序號就又回到了0。

    3、確認號:當ACK標誌位為1時有效,表示期望收到的下一個報文段的第一個資料位元組的序號。確認號為N,則表明到序號N-1為止的所有資料位元組都已經被正確地接收到了。

    4、頭部長度:TCP報文段的頭部長度,它指出TCP報文段的資料部分的起始位置與TCP報文段的起始位置的距離。頭部長度佔4個位元組,但它的單位是32位字,即以4位元組為計算單位,因此頭部長度的最大值為15*4=60個位元組,這就意味著選項的長度不超過40個位元組。

    5、保留位:必須為0.

    6、下面的六個控制位說明報文段的性質:

    1)URG:與首部中的緊急指標欄位配合使用。URG為1時,表明緊急指標欄位有效,傳送應用程式告訴傳送方的TCP有緊急資料要傳送,於是傳送方TCP就把緊急資料插入到本報文段資料的最前面,而其後面仍是普通資料。

    2)ACK:僅當ACK=1時確認號欄位才有效,當ACK=0時,確認號無效。TCP規定,在連線建立後所有的傳送報文段都必須把ACK置1。

    3)PSH:如果傳送的報文段中PSH為1,則接收方接受到該報文段後,直接將其交付給應用程式,而不再等待整個快取都填滿後再向上交付。

    4)RST:復位標誌,RST=1時,表明TCP連線中出現嚴重差錯,必須釋放連線,然後重新建立運輸連線。

    5)SYN:同步序號,用來發起一個連線。當SYN=1而ACK=0時,表明這是一個連線請求報文段,若對方同意建立連線,則應在響應的報文段中使SYN=1和ACK=1。

    6)FIN:用來釋放一個連線。當FIN=1時,表明此報文段的傳送方的資料已傳送完畢,並要求釋放連線。

    7、視窗:接收方讓傳送方下次傳送報文段時設定的傳送視窗的大小。

    8、校驗和:校驗的欄位範圍包括首部和資料這兩部分。

    9、緊急指標:緊急指標當URG=1時才有效,它指出本報文段中的緊急資料的位元組數。值得注意的是,即使視窗為0時,也可傳送緊急資料。

    10、選項與填充:選項應該為4位元組的整數倍,否則用0填充。最常見的可選欄位是最長報文大小MSS(Maximum Segment Size),每個連線方通常都在通訊的第一個報文段中指明這個選項。它指明本端所能接收的最大長度的報文段。該選項如果不設定,預設為536(20+20+536=576位元組的IP資料包),其中ip首部和tcp首部各20個位元組,而internet 上標準的MTU (最小)為576B。  


TCP連線的建立

    下圖為TCP三次握手連線的建立過程:


    服務端的TCP程式先建立傳輸控制塊TCB,準備接受客戶端程式的連線請求,然後服務端程式處於LISTEN狀態,等待客戶端的連線請求,如有,則作出響應。

    1、客戶端的TCP程式也首先建立傳輸控制模組TCB,然後向服務端發出連線請求報文段,該報文段首部中的SYN=1,ACK=0,同時選擇一個初始序號seq=i。TCP規定,SYN=1的報文段不能攜帶資料,但要消耗掉一個序號。這時,TCP客戶程式進入SYN—SENT(同步已傳送)狀態,這是TCP連線的第一次握手。

    2、服務端收到客戶端發來的請求報文後,如果同意建立連線,則向客戶端傳送確認。確認報文中的SYN=1,ACK=1,確認號ack=i+1,同時為自己選擇一個初始序號seq=j。同樣該報文段也是SYN=1的報文段,不能攜帶資料,但同樣要消耗掉一個序號。這時,TCP服務端進入SYN—RCVD(同步收到)狀態,這是TCP連線的第二次握手。

    3、TCP客戶端程式收到服務端程式的確認後,還要向服務端給出確認。確認報文段的ACK=1,確認號ack=j+1,而自己的序號為seq=i+1。TCP的標準規定,ACK報文段可以攜帶資料,但如果不攜帶資料則不消耗序號,因此,如果不攜帶資料,則下一個報文段的序號仍為seq=i+1。這時,TCP連線已經建立,客戶端進入ESTABLISHED(已建立連線)狀態。這是TCP連線的第三次握手,可以看出第三次握手客戶端已經可以傳送攜帶資料的報文段了。

    當服務端收到確認後,也進入ESTABLISHED(已建立連線)狀態。


    雙方同時主動連線的TCP連線建立過程
    正常情況下,傳輸連線都是由一方主動發起的,但也有可能雙方同時主動發起連線,此時就會發生連線碰撞,最終只有一個連線能夠建立起來。因為所有連線都是由它們的端點進行標識的。如果第一個連線請求建立起一個由套接字(x,y)標識的連線,而第二個連線也建立了這樣一個連線,那麼在TCP實體內部只有一個套接字表項。
當出現同時發出連線請求時,則兩端幾乎在同時傳送一個SYN欄位置1的資料段,並進入SYN_SENT狀態。當每一端收到SYN資料段時,狀態變為SYN_RCVD,同時它們都再傳送SYN欄位置1,ACK欄位置1的資料段,對收到的SYN資料段進行確認。當雙方都收到對方的SYN+ACK資料段後,便都進入ESTABLISHED狀態。圖10-39顯示了這種同時發起連線的連線過程,但最終建立的是一個TCP連線,而不是兩個,這點要特別注意。

    從圖中可以看出,一個雙方同時開啟的傳輸連線需要交換4資料段,比正常的傳輸連線建立所進行的三次握手多交換一個資料段。此外要注意的是,此時我們沒有將任何一端稱為客戶或伺服器,因為每一端既是客戶又是伺服器。


    為什麼一定要進行三次握手呢?

    前兩次的握手很顯然是必須的,主要是最後一次,即客戶端收到服務端發來的確認後為什麼還要想服務端再傳送一次確認呢?這主要是為了防止已失效的請求報文段突然又傳送到了服務端而產生連線的誤判。

    考慮如下的情況:客戶端傳送了一個連線請求報文段到服務端,但是在某些網路節點上長時間滯留了,而後客戶端又超時重發了一個連線請求報文段該服務端,而後正常建立連線,資料傳輸完畢,並釋放了連線。如果這時候第一次傳送的請求報文段延遲了一段時間後,又到了服務端,很顯然,這本是一個早已失效的報文段,但是服務端收到後會誤以為客戶端又發出了一次連線請求,於是向客戶端發出確認報文段,並同意建立連線。假設不採用三次握手,這時服務端只要傳送了確認,新的連線就建立了,但由於客戶端比你更沒有發出建立連線的請求,因此不會理會服務端的確認,也不會向服務端傳送資料,而服務端卻認為新的連線已經建立了,並在一直等待客戶端傳送資料,這樣服務端就會一直等待下去,直到超出保活計數器的設定值,而將客戶端判定為出了問題,才會關閉這個連線。這樣就浪費了很多伺服器的資源。而如果採用三次握手,客戶端就不會向服務端發出確認,服務端由於收不到確認,就知道客戶端沒有要求建立連線,從而不建立該連線。


TCP連線的釋放

    下圖為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連線的第四次揮手。


    雙方主動關閉的TCP連線釋放流程

    與可以雙方同時建立TCP傳輸連線一樣,TCP傳輸連線關閉也可以由雙方同時主動進行(正常情況下都是由一方傳送第一個FIN資料段進行主動連線關閉,另一方被動接受連線關閉)


    當兩端對應的網路應用層程式同時呼叫CLOSE原語,傳送FIN資料段執行關閉命令時,兩端均從ESTABLISHED狀態轉變為FIN WAIT 1狀態。任意一方收到對端發來的FIN資料段後,其狀態均由FIN WAIT 1轉變到CLOSING狀態,併傳送最後的ACK資料段。當收到最後的ACK資料段後,狀態轉變化TIME_WAIT,在等待2MSL後進入到CLOSED狀態,最終釋放整個TCP傳輸連線。


    為什麼A在TIME—WAIT狀態必須等待2MSL時間呢?

    1、為了保證A傳送的最後一個ACK報文段能夠到達B。該ACK報文段很有可能丟失,因而使處於在LIST—ACK狀態的B收不到對已傳送的FIN+ACK報文段的確認,B可能會重傳這個FIN+ACK報文段,而A就在這2MSL時間內收到這個重傳的FIN+ACK報文段,接著A重傳一次確認,重新啟動2MSL計時器,最後A和B都進入CLOSED狀態。如果A在TIME—WAIT狀態不等待一段時間就直接釋放連線,到CLOSED狀態,那麼久無法收到B重傳的FIN+ACK報文段,也就不會再傳送一次確認ACK報文段,B就無法正常進入CLOSED狀態。

    2、防止已失效的請求連線出現在本連線中。在連線處於2MSL等待時,任何遲到的報文段將被丟棄,因為處於2MSL等待的、由該插口(插口是IP和埠對的意思,socket)定義的連線在這段時間內將不能被再用,這樣就可以使下一個新的連線中不會出現這種舊的連線之前延遲的報文段。

    補充:

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

    

    

    


相關文章