TCP協議建立連線(三次握手)和斷開連線(四次揮手)
首先先看下TCP的報頭格式
發現在中間有一個6位的標誌位(紅色標出)
標誌位欄位 | 含義 |
---|---|
URG | 緊急指標是否有效;置為1表示要優先處理 |
ACK | 確認號是否有效;設為1表示為確認應答報文(通常情況下會設為1,但是第一次的TCP不會設為1) |
PSH | 提示接收端應用程式立刻從TCP緩衝區把資料讀走(若接收端緩衝區資料長時間未處理,那麼傳送端將強制將資料交付) |
RST | 復位報文段,對方要求重新建立連線;置為1表示要復位 |
SYN | 同步報文段,請求建立連線;置為1表示連線請求報文,其他型別置為0 |
FIN | 結束報文,通知對方,本端要關閉了(是根據五元組(源IP,目的IP,源埠號,目的埠號,協議號)進行關閉) |
TCP建立連線和斷開連線流程
1 三次握手
1.1 三次握手過程
首先服務端和客戶端都處於CLOSED狀態
1.1.1 第一次握手
客戶端:客戶端傳送請求建立連線,想要請求連線,需要傳送SYN包到伺服器,此時客戶端的狀態為SYN_SEND狀態
為什麼是客戶端請求連線,而不是服務端請求連線??
想想現實中的例子,比如qq,都是使用者想連線上qq伺服器,但是qq伺服器不會主動想要連線一個使用者服務端:服務端在最開始的時候是ClOSE狀態,然後是處於監聽狀態LISTEN,然後接收到客戶端傳送的SYN包,伺服器端變為SYN_RECVE狀態
為什麼服務端要處於監聽狀態??
因為伺服器端的某個SOCKET處於監聽狀態,表示可以接受連線了
注:在第一次握手的時候ACK是為0的狀態,因為ACK是確認應答報文,第一次握手的過程是客戶端向服務端傳送連線請求,連連線都還沒有建立,那麼就沒有確認應答這一說了
1.1.2 第二次握手
服務端:服務端此時狀態為SYN_RECVE狀態,此時接收到了客戶端已經發送過來的SYN包,此時服務端要做的是對客戶端的SYN包進行確認(ACK),並且也傳送一個STY包
服務端在第一次握手時收到客戶端傳送的SYN(請求連線)之後為什麼還要再向客戶端傳送SYN請求??
建立連線是一個相互的過程,開始是客戶端請求連線,服務端收到後又要再次向客戶端傳送請求,雙方都需要請求連線客戶端:此時客戶端接收到服務端傳送的確認應答(ACK),和一個SYN包,此時客戶端的狀態變為ESTABLISHED(已建立連線)狀態
1.1.3 第三次握手
- 客戶端:客戶端此時的狀態為ESTABLISHED,此時收到了服務端傳送的確認應答和服務端的連線請求,那麼要想建立連線,客戶端就向服務端傳送一個確認應答(ACK)
- 服務端:服務端此時接收到客戶端的確認應答(ACK),表示請求收到了,那麼此時的狀態變為ESTABLISHED狀態,表示連線建立,此時雙方連線建立完成,可以開始通訊了
1.2 三次握手舉例(兩個人打電話)
1.3 三次握手的一些問題
為什麼是三次握手不是兩次握手或者四次握手或者更多??
先看兩次握手的情況:
- 客戶端向服務端傳送SYN連線請求
服務端接收,並向客戶端傳送確認應答報文ACK和再次向服務端傳送連線請求
兩次握手的過程就是上面的兩個過程,但是我們知道TCP的報文傳送是有可能發生丟失問題的
(1)現在假設第一步出現丟失情況:客戶端向服務端傳送的SYN包發生丟失,此時服務端不會接收到任何資訊,當然就不會對客戶端的請求做出任何反應,因此雙方都認為連線未達成,不會產生問題
(2)現在假設第二步出現丟失的狀況:服務端給客戶端傳送ACK和SYN發生丟失,此時站在服務端一方看來,服務端認為自己將兩條報文傳送完成了,那麼自己已經完成了連線,所以伺服器端會維護一個連結;但是此時因為ACK和SYN(可能是其中一個或者兩個都丟了)客戶端沒有(或收到不全)收到服務端的回覆,那麼站在客戶端看來,雙方並沒有建立完成連線,此時可能又會再次傳送連線請求,如果都恰巧一直髮生這種情況,那麼在服務端會維護無數個實際上並沒有用處的連線,這樣會浪費很大的資源(“SYN的洪水攻擊”)
再看三次握手的情況:
- 客戶端傳送連線請求SYN報文
- 服務端傳送確認應答ACK和連線請求SYN
客戶端對服務端傳送的連線請求SYN傳送ACK確認應答報文
三次握手的過程就是上面寫道的三步,但是在傳輸的過程中依舊可能會發生報文丟失的問題
(1)假設第一步報文出現丟失問題:客戶端傳送請求連線報文SYN,但是在傳輸途中報文丟失,服務端沒有接收到SYN報文,因此不會對客戶端進行響應,因此雙方都認為連線未建立,不會產生問題
(2)假設第二步報文出現丟失問題:服務端接收到客戶端傳送的SYN,但是在給客戶端進行響應回覆的ACK和SYN在傳輸途中丟失了,那麼客戶端也就不會對服務端的傳送進行響應,雙方認為連線未建立,不會產生問題
(3)假設第三部報文出現丟失問題:客戶端對服務端傳送的報文進行響應,站在客戶端來說,ACK確認應答已傳送,那麼客戶端認為連線建立,那麼會在客戶端上維護連線,但是ACK在傳輸途中丟失,那麼服務端未收到確認應答,那麼站在伺服器端,伺服器認為連線為建立成功,那麼伺服器上不會維護連線,那麼不會產生問題(為什麼在客戶端有無用的連線維護不會影響,但是服務端無用連線維護會產生問題??客戶端有很多,而服務端的空間有限)
綜上,三次握手可以
其實四次握手或者多次握手也是可以的,但是並沒有必要,三次足以解決問題,並且每次建立握手都是需要耗費空間的,因此不需要那麼多次
2 四次揮手
2.1 四次揮手過程
所謂四次揮手其實就是客戶端與服務端斷開連線,當然還是客戶端主動要斷開
2.1.1 第一次揮手
- 客戶端:客戶端向服務端傳送結束報文FIN,此時客戶端的狀態為FIN_WAIT
- 服務端:服務端接收FIN,此時狀態為CLOSE_WAIT
2.1.2 第二次揮手
- 服務端:服務端向客戶端傳送斷開連線的確認應答ACK
- 客戶端:客戶端接收服務端傳送的ACK,進入FIN_WAIT2狀態
此時從客戶端到伺服器的連線斷開
2.1.3 第三次揮手
- 服務端:服務端向客戶端傳送結束報文FIN,此時服務端狀態為LAST_ACK
- 客戶端:客戶端接收服務端的結束報文,此時狀態為TIME_WAIT(此時客戶端並不是關閉的狀態,而是TIME_WAIT的狀態)
2.1.4 第四次揮手
- 客戶端:客戶端向服務端傳送確認應答請求
- 服務端:此時服務端狀態變為CLOSED
四次揮手結束,最終客戶端與服務端的狀態都變為CLOSED
2.2 四次揮手的一些問題
2.2.1 為什麼TIME_WAIT狀態要等待2MSL才能回到CLOSED狀態
經過四次揮手過後,按道理說,客戶端的狀態可以直接回到CLOSED的狀態,但是通過上面的過程圖發現,在客戶端在到CLOSED狀態之前還到了TIME_WAIT狀態,這是因為網路很可能是不可靠的,在報文傳輸的時候很有可能會發生報文丟失的情況,無法保證客戶端最後傳送的ACK報文一定會被對方收到,就是說對方處於LAST_ACK 狀態下的SOCKET可能會因為超時未收到ACK報文,而重發FIN報文,所以這個TIME_WAIT 狀態的作用就是用來重發可能丟失的ACK報文。
2.2.2 關閉TCP一定要四次揮手嗎
不一定,4次揮手關閉TCP連線是最安全的做法。但有時是不太希望出現TIME_WAIT 狀態(比如當MSL數值設定過大導致伺服器端有太多TIME_WAIT狀態的TCP連線,減少這些條目數可以更快地關閉連線,為新連線釋放更多資源),這時我們可以通過設定SOCKET變數的SO_LINGER標誌來避免SOCKET在close()之後進入TIME_WAIT狀態,這時將通過傳送RST強制終止TCP連線(取代正常的TCP四次握手的終止方式)。但這種方法並不優先選擇,使用TIME_WAIT的方法是優先考慮的。