1. 程式人生 > >TCP/IP三次握手、四次揮手、11種狀態知識點整理

TCP/IP三次握手、四次揮手、11種狀態知識點整理

做應用層做得比較久了,底層的一些知識點有點遺忘,今天正好有空梳理了一下關於TCP/IP通訊相關的一些知識點。

TCP三次握手建立連線

Tcp頭部

 

六個標誌位中,我們要用到三個:

SYN:SYNchronous,SYN= 1 表示這是一個連線請求或連線接受報文。在建立連線時用來進行同步序號(個人理解是,在建立連線的時候,提醒對方記錄本方的起始序號)。當SYN=1而ACK=0時,表明這是一個連線請求報文段。對方若是同意建立連線,則應響應的報文段中使SYN=1、ACK=1。因此SYN=1表示該報文是一個連線請求報文或者是一個連線請求接收報文。

ACK:ACKnowledgment,確認號只有在該位設定為1的時候才生效,當該位為0是表示確認號無效。TCP規定,在TCP連線建立後所有傳送的資料報文段ACK都必須設定為1。

FIN:FINish,當 FIN = 1 時,表明此報文段的傳送方的資料已經發送完畢,並要求釋放連線。

此外我們還需要用到序號和確認號:

序號:佔4個位元組,它的範圍在0-2^32-1,序號隨著通訊的進行不斷的遞增,當達到最大值的時候重新回到0在開始遞增。TCP是面向位元組流的,在一個TCP連線中傳送的位元組流中的每一個位元組都按照順序編號。整個要傳送的位元組流的起始號必須在連線建立時設定。首部中的序列號欄位指的是本報文段所傳送的資料的第一個位元組的序號。例如,一個報文序號是301,而攜帶的資料共有100位元組。則表示本次報文中的序號是301,下一個報文的序號是401.重複一下,每一個報文的序號是該報文包含的位元組中第一個位元組的編號。

確認號:佔4個位元組,確認號,是對下一個想要接受的位元組的期望,這裡隱式確認了對上一個資料包的成功接收。如上例,在成功接收了序號為301的資料包,想要接收下一個資料包因為上個數據包包含100位元組,所以此時的確認號應該是401,表示希望接收下一個序號是401的資料包。

三次握手過程:

 

過程描述:

首先由Client發出請求連線即 SYN=1 ACK=0  (請看頭欄位的介紹),TCP規定SYN=1時不能攜帶資料,但要消耗一個序號,因此宣告自己的序號是 seq=x。

然後 Server 進行回覆確認,即 SYN=1 ACK=1 seq=y,ack=x + 1。

再然後 Client 再進行一次確認,但不用SYN 了,這時即為 ACK=1,seq=x + 1,ack=y + 1。

為什麼要進行三次握手(兩次確認):

為什麼A還要傳送一側確認呢?這主要是為了防止已失效的連線請求報文突然又傳送到了B,因而產生錯誤。

所謂“已失效的連線請求報文段”是這樣產生的。考慮一種正常情況。A發出連線請求,但因連線請求丟失而未收到確認。於是A再次重傳一次連線請求。後來收到了確認建立了連線。資料傳輸完畢後,就釋放了連線。A共傳送了兩個連線請求的報文段,其中第一個丟失,第二個到達了B。沒有“已失效的連線請求報文段”。

現假定出現一種異常情況,即A發出的第一個連線請求報文段並沒有丟失,而是在某些網路節點長時間滯留了,以致延誤到連線釋放以後的某個時間才到B。本來這是一個已失效的報文段。但是B收到此失效的連線請求報文段後,就誤認為是A有發出一次新的連線請求。於是就向A發出確認報文段,同意建立連線。假定不採用三次握手,那麼只要B發出確認,新的連線就建立了。

由於現在A並沒有發出建立連線的請求,因此不會理睬B的確認,也不會向B傳送資料。但B卻以為新的運輸連線已經建立了,並一直等待A發來資料。B的許多資源就這樣白白浪費了。

採用三次握手的辦法可以防止上述現象的發生。例如在剛才的情況下,A不會向B的確認發出確認。B由於收不到確認,就知道A並沒有要求建立連線。

另一種解釋:

這個問題的本質是, 通道不可靠, 但是通訊雙發需要就某個問題達成一致. 而要解決這個問題, 無論你在訊息中包含什麼資訊, 三次通訊是理論上的最小值。所以三次握手不是TCP本身的要求, 而是為了滿足”在不可靠通道上可靠地傳輸資訊”這一需求所導致的。請注意這裡的本質需求,通道不可靠, 資料傳輸要可靠。三次達到了, 那後面你想接著握手也好, 發資料也好, 跟進行可靠資訊傳輸的需求就沒關係了。因此,如果通道是可靠的, 即無論什麼時候發出訊息, 對方一定能收到, 或者你不關心是否要保證對方收到你的訊息, 那就能像UDP那樣直接傳送訊息就可以了”。這可視為對“三次握手”目的的另一種解答思路。

四次揮手關閉連線

 

當客戶A 沒有東西要傳送時就要釋放 A 這邊的連線,A會發送一個報文(沒有資料),其中 FIN 設定為1,  伺服器B收到後會給應用程式一個信,這時A那邊的連線已經關閉,即A不再發送資訊(但仍可接收資訊)。  A收到B的確認後進入等待狀態,等待B請求釋放連線, B資料傳送完成後就向A請求連線釋放,也是用FIN=1 表示, 並且用 ack = u + 1(如圖), A收到後回覆一個確認資訊,並進入 TIME_WAIT 狀態, 等待 2MSL(2 Maximum Segment Lifetime)時間

l  為什麼要等待呢?

l  為了防止這種情況:A接到B的釋放連線請求後會傳送一個確認資訊,但是如果這個確認資訊丟了,也就是B沒有收到確認釋放連線,那麼B就會重發一個釋放連線請求,這時候A還處於TIME_WAIT狀態,所以會再次傳送一個確認資訊。

l  Q2為什麼TIME_WAIT 狀態還需要等2*MSL秒之後才能返回到CLOSED 狀態呢?

l  A2因為雖然雙方都同意關閉連線了,而且握手的4個報文也都發送完畢,按理可以直接回到CLOSED 狀態(就好比從SYN_SENT 狀態到ESTABLISH 狀態那樣),但是我們必須假想網路是不可靠的,你無法保證你最後傳送的ACK報文一定會被對方收到,就是說對方處於LAST_ACK 狀態下的SOCKET可能會因為超時未收到ACK報文,而重發FIN報文,所以這個TIME_WAIT 狀態的作用就是用來重發可能丟失的ACK報文。

11種狀態

 

簡單解釋:

l  CLOSED:初始狀態,表示TCP連線是“關閉著的”或“未開啟的”。

l  LISTEN :表示伺服器端的某個SOCKET處於監聽狀態,可以接受客戶端的連線。

l  SYN_RCVD :表示伺服器接收到了來自客戶端請求連線的SYN報文。在正常情況下,這個狀態是伺服器端的SOCKET在建立TCP連線時的三次握手會話過程中的一箇中間狀態,很短暫,基本上用netstat很難看到這種狀態,除非故意寫一個監測程式,將三次TCP握手過程中最後一個ACK報文不予傳送。當TCP連線處於此狀態時,再收到客戶端的ACK報文,它就會進入到ESTABLISHED 狀態。

l  SYN_SENT :這個狀態與SYN_RCVD 狀態相呼應,當客戶端SOCKET執行connect()進行連線時,它首先發送SYN報文,然後隨即進入到SYN_SENT 狀態,並等待服務端的傳送三次握手中的第2個報文。SYN_SENT 狀態表示客戶端已傳送SYN報文。

l  ESTABLISHED :表示TCP連線已經成功建立。

l  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看到。

l  FIN_WAIT_2 :上面已經解釋了這種狀態的由來,實際上FIN_WAIT_2狀態下的SOCKET表示半連線,即有一方呼叫close()主動要求關閉連線。注意:FIN_WAIT_2 是沒有超時的(不像TIME_WAIT 狀態),這種狀態下如果對方不關閉(不配合完成4次揮手過程),那這個 FIN_WAIT_2 狀態將一直保持到系統重啟,越來越多的FIN_WAIT_2 狀態會導致核心crash。

l  TIME_WAIT :表示收到了對方的FIN報文,併發送出了ACK報文。 TIME_WAIT狀態下的TCP連線會等待2*MSL(Max Segment Lifetime,最大分段生存期,指一個TCP報文在Internet上的最長生存時間。每個具體的TCP協議實現都必須選擇一個確定的MSL值,RFC 1122建議是2分鐘,但BSD傳統實現採用了30秒,Linux可以cat /proc/sys/net/ipv4/tcp_fin_timeout看到本機的這個值),然後即可回到CLOSED 可用狀態了。如果FIN_WAIT_1狀態下,收到了對方同時帶FIN標誌和ACK標誌的報文時,可以直接進入到TIME_WAIT狀態,而無須經過FIN_WAIT_2狀態。(這種情況應該就是四次揮手變成三次揮手的那種情況)

l CLOSING :這種狀態在實際情況中應該很少見,屬於一種比較罕見的例外狀態。正常情況下,當一方傳送FIN報文後,按理來說是應該先收到(或同時收到)對方的ACK報文,再收到對方的FIN報文。但是CLOSING 狀態表示一方傳送FIN報文後,並沒有收到對方的ACK報文,反而卻也收到了對方的FIN報文。什麼情況下會出現此種情況呢?那就是當雙方几乎在同時close()一個SOCKET的話,就出現了雙方同時傳送FIN報文的情況,這是就會出現CLOSING 狀態,表示雙方都正在關閉SOCKET連線。

l  CLOSE_WAIT :表示正在等待關閉。怎麼理解呢?當對方close()一個SOCKET後傳送FIN報文給自己,你的系統毫無疑問地將會迴應一個ACK報文給對方,此時TCP連線則進入到CLOSE_WAIT狀態。接下來呢,你需要檢查自己是否還有資料要傳送給對方,如果沒有的話,那你也就可以close()這個SOCKET併發送FIN報文給對方,即關閉自己到對方這個方向的連線。有資料的話則看程式的策略,繼續傳送或丟棄。簡單地說,當你處於CLOSE_WAIT 狀態下,需要完成的事情是等待你去關閉連線。

l  LAST_ACK :當被動關閉的一方在傳送FIN報文後,等待對方的ACK報文的時候,就處於LAST_ACK 狀態。當收到對方的ACK報文後,也就可以進入到CLOSED 可用狀態了。

CLOSING狀態:

 

參考連結: