1. 程式人生 > >Http協議三次握手與四次揮手

Http協議三次握手與四次揮手

首先要知道 三次握手是‘誰’跟‘誰’進行的協議 ,接下來就跟大家講解 三次握手與四次揮手

TCP(Transmission Control Protocol) 傳輸控制協議

TCP是主機對主機層的傳輸控制協議,提供可靠的連線服務,採用三次握手確認建立一個連線。

接下來認識下TCP標誌位,有6種標示:

SYN(synchronous建立聯機)

ACK(acknowledgement 確認)

PSH(push傳送)

FIN(finish結束)

RST(reset重置)

URG(urgent緊急)

TCP第一次握手:主機A傳送位碼為syn=1,隨機產生seq number=1234567的資料包到伺服器,主機B由SYN=1

知道,A要求建立聯機;

第二次握手:主機B收到請求後要確認聯機資訊,向A傳送ack number=(主機A的seq+1),syn=1,ack=1,隨機產生seq=7654321的包

第三次握手:主機A收到後檢查ack number是否正確,即第一次傳送的seq number+1,以及位碼ack是否為1,若正確,主機A會再發送ack number=(主機B的seq+1),ack=1,主機B收到後確認seq值與ack=1則連線建立成功。

完成三次握手,主機A與主機B開始傳送資料。
在TCP協議中第一次握手:建立連線時,客戶端傳送syn包(syn=j)到伺服器,並進入SYN_SEND狀態,等待伺服器確認;
第二次握手:伺服器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也傳送一個SYN包(syn=k),即SYN+ACK包,此時伺服器進入SYN_RECV狀態; 第三次握手:客戶端收到伺服器的SYN+ACK包,向伺服器傳送確認包ACK(ack=k+1),此包傳送完畢,客戶端和伺服器進入ESTABLISHED狀態,完成三次握手。 完成三次握手,客戶端與伺服器開始傳送資料.

IP 192.168.1.116.3337 > 192.168.1.123.7788: S 3626544836:3626544836
IP 192.168.1.123.7788 > 192.168.1.116.3337: S 1739326486:1739326486 ack 3626544837
IP 192.168.1.116.3337 > 192.168.1.123.7788: ack 1739326487,ack 1

第一次握手:192.168.1.116傳送位碼syn=1,隨機產生seq number=3626544836的資料包到192.168.1.123,   192.168.1.123

由SYN=1知道192.168.1.116要求建立聯機;

第二次握手:192.168.1.123收到請求後要確認聯機資訊,向192.168.1.116傳送ack number=3626544837,syn=1,ack=1,隨機產生seq=1739326486的包;

第三次握手:192.168.1.116收到後檢查ack number是否正確,即第一次傳送的seq number+1,以及位碼ack是否為1,若正確,192.168.1.116會再發送ack number=1739326487,ack=1,192.168.1.123收到後確認seq=seq+1,ack=1則連線建立成功。

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

 CP的連線的拆除需要傳送四個包,因此稱為四次揮手(four-way handshake)。客戶端或伺服器均可主動發起揮手動作,在socket程式設計中,任何一方執行close()操作即可產生揮手操作。

(1)客戶端A傳送一個FIN,用來關閉客戶A到伺服器B的資料傳送。 

(2)伺服器B收到這個FIN,它發回一個ACK,確認序號為收到的序號加1。和SYN一樣,一個FIN將佔用一個序號。 

(3)伺服器B關閉與客戶端A的連線,傳送一個FIN給客戶端A。 

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

TCP採用四次揮手關閉連線如圖2所示。

 圖2  TCP四次揮手關閉連線

參見wireshark抓包,實測的抓包結果並沒有嚴格按揮手時序。我估計是時間間隔太短造成。

深入理解TCP連線的釋放: 

由於TCP連線是全雙工的,因此每個方向都必須單獨進行關閉。這原則是當一方完成它的資料傳送任務後就能傳送一個FIN來終止這個方向的連線。收到一個 FIN只意味著這一方向上沒有資料流動,一個TCP連線在收到一個FIN後仍能傳送資料。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。
TCP協議的連線是全雙工連線,一個TCP連線存在雙向的讀寫通道。 
簡單說來是 “先關讀,後關寫”,一共需要四個階段。以客戶機發起關閉連線為例:
1.伺服器讀通道關閉
2.客戶機寫通道關閉
3.客戶機讀通道關閉
4.伺服器寫通道關閉
關閉行為是在發起方資料傳送完畢之後,給對方發出一個FIN(finish)資料段。直到接收到對方傳送的FIN,且對方收到了接收確認ACK之後,雙方的資料通訊完全結束,過程中每次接收都需要返回確認資料段ACK。
詳細過程:
    第一階段   客戶機發送完資料之後,向伺服器傳送一個FIN資料段,序列號為i
    1.伺服器收到FIN(i)後,返回確認段ACK,序列號為i+1關閉伺服器讀通道
    2.客戶機收到ACK(i+1)後,關閉客戶機寫通道
   (此時,客戶機仍能通過讀通道讀取伺服器的資料,伺服器仍能通過寫通道寫資料)
    第二階段 伺服器傳送完資料之後,向客戶機發送一個FIN資料段,序列號為j;
    3.客戶機收到FIN(j)後,返回確認段ACK,序列號為j+1關閉客戶機讀通道
    4.伺服器收到ACK(j+1)後,關閉伺服器寫通道
這是標準的TCP關閉兩個階段,伺服器和客戶機都可以發起關閉,完全對稱。
FIN標識是通過傳送最後一塊資料時設定的,標準的例子中,伺服器還在傳送資料,所以要等到傳送完的時候,設定FIN(此時可稱為TCP連線處於半關閉狀態,因為資料仍可從被動關閉一方向主動關閉方傳送)。如果在伺服器收到FIN(i)時,已經沒有資料需要傳送,可以在返回ACK(i+1)的時候就設定FIN(j)標識,這樣就相當於可以合併第二步和第三步。
讀《Linux網路程式設計》關閉TCP連線章節,作以下筆記:

TCP的TIME_WAIT和Close_Wait狀態

面試時看到應聘者簡歷中寫精通網路,TCP程式設計,我常問一個問題,TCP建立連線需要幾次握手?95%以上的應聘者都能答對是3次。問TCP斷開連線需要幾次握手,70%的應聘者能答對是4次通訊。再問CLOSE_WAIT,TIME_WAIT是什麼狀態,怎麼產生的,對服務有什麼影響,如何消除?有一部分同學就回答不上來。不是我扣細節,而是在通訊為主的前端伺服器上,必須有能力處理各種TCP狀態。比如統計在本廠的一臺前端機上高峰時間TCP連線的情況,統計命令:

  1. netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'  

結果:

除了ESTABLISHED,可以看到連線數比較多的幾個狀態是:FIN_WAIT1, TIME_WAIT, CLOSE_WAIT, SYN_RECV和LAST_ACK;下面的文章就這幾個狀態的產生條件、對系統的影響以及處理方式進行簡單描述。

TCP狀態

TCP狀態如下圖所示:

可能有點眼花繚亂?再看看這個時序圖

下面看下大家一般比較關心的三種TCP狀態

SYN_RECV 

服務端收到建立連線的SYN沒有收到ACK包的時候處在SYN_RECV狀態。有兩個相關係統配置:

1,net.ipv4.tcp_synack_retries :INTEGER

預設值是5

對於遠端的連線請求SYN,核心會發送SYN + ACK資料報,以確認收到上一個 SYN連線請求包。這是所謂的三次握手( threeway handshake)機制的第二個步驟。這裡決定核心在放棄連線之前所送出的 SYN+ACK 數目。不應該大於255,預設值是5,對應於180秒左右時間。通常我們不對這個值進行修改,因為我們希望TCP連線不要因為偶爾的丟包而無法建立。

2,net.ipv4.tcp_syncookies

一般伺服器都會設定net.ipv4.tcp_syncookies=1來防止SYN Flood攻擊。假設一個使用者向伺服器傳送了SYN報文後突然宕機或掉線,那麼伺服器在發出SYN+ACK應答報文後是無法收到客戶端的ACK報文的(第三次握手無法完成),這種情況下伺服器端一般會重試(再次傳送SYN+ACK給客戶端)並等待一段時間後丟棄這個未完成的連線,這段時間的長度我們稱為SYN Timeout,一般來說這個時間是分鐘的數量級(大約為30秒-2分鐘)。

這些處在SYNC_RECV的TCP連線稱為半連線,並存儲在核心的半連線佇列中,在核心收到對端傳送的ack包時會查詢半連線佇列,並將符合的requst_sock資訊儲存到完成三次握手的連線的佇列中,然後刪除此半連線。大量SYNC_RECV的TCP連線會導致半連線佇列溢位,這樣後續的連線建立請求會被核心直接丟棄,這就是SYN Flood攻擊。

能夠有效防範SYN Flood攻擊的手段之一,就是SYN Cookie。SYN Cookie原理由D. J. Bernstain和 Eric Schenk發明。SYN Cookie是對TCP伺服器端的三次握手協議作一些修改,專門用來防範SYN Flood攻擊的一種手段。它的原理是,在TCP伺服器收到TCP SYN包並返回TCP SYN+ACK包時,不分配一個專門的資料區,而是根據這個SYN包計算出一個cookie值。在收到TCP ACK包時,TCP伺服器在根據那個cookie值檢查這個TCP ACK包的合法性。如果合法,再分配專門的資料區進行處理未來的TCP連線。

觀測服務上SYN_RECV連線個數為:7314,對於一個高併發連線的通訊伺服器,這個數字比較正常。

CLOSE_WAIT

發起TCP連線關閉的一方稱為client,被動關閉的一方稱為server。被動關閉的server收到FIN後,但未發出ACK的TCP狀態是CLOSE_WAIT。出現這種狀況一般都是由於server端程式碼的問題,如果你的伺服器上出現大量CLOSE_WAIT,應該要考慮檢查程式碼。

TIME_WAIT

根據TCP協議定義的3次握手斷開連線規定,發起socket主動關閉的一方 socket將進入TIME_WAIT狀態。TIME_WAIT狀態將持續2個MSL(Max Segment Lifetime),在Windows下預設為4分鐘,即240秒。TIME_WAIT狀態下的socket不能被回收使用. 具體現象是對於一個處理大量短連線的伺服器,如果是由伺服器主動關閉客戶端的連線,將導致伺服器端存在大量的處於TIME_WAIT狀態的socket, 甚至比處於Established狀態下的socket多的多,嚴重影響伺服器的處理能力,甚至耗盡可用的socket,停止服務。

為什麼需要TIME_WAIT?TIME_WAIT是TCP協議用以保證被重新分配的socket不會受到之前殘留的延遲重發報文影響的機制,是必要的邏輯保證。

和TIME_WAIT狀態有關的系統引數有一般由3個,本廠設定如下:

net.ipv4.tcp_tw_recycle = 1

net.ipv4.tcp_tw_reuse = 1

net.ipv4.tcp_fin_timeout = 30

net.ipv4.tcp_fin_timeout,預設60s,減小fin_timeout,減少TIME_WAIT連線數量。

net.ipv4.tcp_tw_reuse = 1表示開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連線,預設為0,表示關閉;

net.ipv4.tcp_tw_recycle = 1表示開啟TCP連線中TIME-WAIT sockets的快速回收,預設為0,表示關閉。

為了方便描述,我給這個TCP連線的一端起名為Client,給另外一端起名為Server。上圖描述的是Client主動關閉的過程,FTP協議中就這樣的。如果要描述Server主動關閉的過程,只要交換描述過程中的Server和Client就可以了,HTTP協議就是這樣的。

描述過程:
Client呼叫close()函式,給Server傳送FIN,請求關閉連線;Server收到FIN之後給Client返回確認ACK,同時關閉讀通道(不清楚就去看一下shutdown和close的差別),也就是說現在不能再從這個連線上讀取東西,現在read返回0。此時Server的TCP狀態轉化為CLOSE_WAIT狀態。
Client收到對自己的FIN確認後,關閉 寫通道,不再向連線中寫入任何資料。
接下來Server呼叫close()來關閉連線,給Client傳送FIN,Client收到後給Server回覆ACK確認,同時Client關閉讀通道,進入TIME_WAIT狀態。
Server接收到Client對自己的FIN的確認ACK,關閉寫通道,TCP連線轉化為CLOSED,也就是關閉連線。
Client在TIME_WAIT狀態下要等待最大資料段生存期的兩倍,然後才進入CLOSED狀態,TCP協議關閉連線過程徹底結束。

以上就是TCP協議關閉連線的過程,現在說一下TIME_WAIT狀態。
從上面可以看到,主動發起關閉連線的操作的一方將達到TIME_WAIT狀態,而且這個狀態要保持Maximum Segment Lifetime的兩倍時間。為什麼要這樣做而不是直接進入CLOSED狀態?

原因有二:
一、保證TCP協議的全雙工連線能夠可靠關閉
二、保證這次連線的重複資料段從網路中消失

先說第一點,如果Client直接CLOSED了,那麼由於IP協議的不可靠性或者是其它網路原因,導致Server沒有收到Client最後回覆的ACK。那麼Server就會在超時之後繼續傳送FIN,此時由於Client已經CLOSED了,就找不到與重發的FIN對應的連線,最後Server就會收到RST而不是ACK,Server就會以為是連線錯誤把問題報告給高層。這樣的情況雖然不會造成資料丟失,但是卻導致TCP協議不符合可靠連線的要求。所以,Client不是直接進入CLOSED,而是要保持TIME_WAIT,當再次收到FIN的時候,能夠保證對方收到ACK,最後正確的關閉連線。

再說第二點,如果Client直接CLOSED,然後又再向Server發起一個新連線,我們不能保證這個新連線與剛關閉的連線的埠號是不同的。也就是說有可能新連線和老連線的埠號是相同的。一般來說不會發生什麼問題,但是還是有特殊情況出現:假設新連線和已經關閉的老連線埠號是一樣的,如果前一次連線的某些資料仍然滯留在網路中,這些延遲資料在建立新連線之後才到達Server,由於新連線和老連線的埠號是一樣的,又因為TCP協議判斷不同連線的依據是socket pair,於是,TCP協議就認為那個延遲的資料是屬於新連線的,這樣就和真正的新連線的資料包發生混淆了。所以TCP連線還要在TIME_WAIT狀態等待2倍MSL,這樣可以保證本次連線的所有資料都從網路中消失。