【TCP】談談tcp的三次握手和四次揮手

tcp的三次握手過程說一下:

image.png
  • 一開始,客戶端和服務端都處于 CLOSE 狀態。先是服務端主動監聽某個端口,處于 LISTEN 狀態

  • 客戶端會隨機初始化序號(client_isn),將此序號置于 TCP 首部的「序號」字段中,同時把 SYN 標志位置為 1,表示 SYN 報文。接著把第一個 SYN 報文發送給服務端,表示向服務端發起連接,該報文不包含應用層數據,之后客戶端處于 SYN-SENT 狀態。

  • 服務端收到客戶端的 SYN 報文后,首先服務端也隨機初始化自己的序號(server_isn),將此序號填入 TCP 首部的「序號」字段中,其次把 TCP 首部的「確認應答號」字段填入 client_isn + 1, 接著把 SYN 和 ACK 標志位置為 1。最后把該報文發給客戶端,該報文也不包含應用層數據,之后服務端處于 SYN-RCVD 狀態。

  • 客戶端收到服務端報文后,還要向服務端回應最后一個應答報文,首先該應答報文 TCP 首部 ACK 標志位置為 1 ,其次「確認應答號」字段填入 server_isn + 1 ,最后把報文發送給服務端,這次報文可以攜帶客戶到服務端的數據,之后客戶端處于 ESTABLISHED 狀態。

  • 服務端收到客戶端的應答報文后,也進入 ESTABLISHED 狀態。

tcp的四次揮手的各個狀態說一下:


image.png

具體過程:

  • 客戶端主動調用關閉連接的函數,于是就會發送 FIN 報文,這個 FIN 報文代表客戶端不會再發送數據了,進入 FIN_WAIT_1 狀態;

  • 服務端收到了 FIN 報文,然后馬上回復一個 ACK 確認報文,此時服務端進入 CLOSE_WAIT 狀態。在收到 FIN 報文的時候,TCP 協議棧會為 FIN 包插入一個文件結束符 EOF 到接收緩沖區中,服務端應用程序可以通過 read 調用來感知這個 FIN 包,這個 EOF 會被放在已排隊等候的其他已接收的數據之后,所以必須要得繼續 read 接收緩沖區已接收的數據;

  • 然后,當服務端在 read 數據的時候,最后自然就會讀到 EOF,接著 read() 就會返回 0,這時服務端應用程序如果有數據要發送的話,就發完數據后才調用關閉連接的函數,如果服務端應用程序沒有數據要發送的話,可以直接調用關閉連接的函數,這時服務端就會發一個 FIN 包,這個 FIN 報文代表服務端不會再發送數據了,之后處于 LAST_ACK 狀態;

  • 客戶端接收到服務端的 FIN 包,并發送 ACK 確認包給服務端,此時客戶端將進入 TIME_WAIT 狀態;

  • 服務端收到 ACK 確認包后,就進入了最后的 CLOSE 狀態;

  • 客戶端經過 2MSL 時間之后,也進入 CLOSE 狀態。

為什么創建連接是三次握手?

三次握手的原因:

  • 三次握手才可以阻止重復歷史連接的初始化(主要原因)
  • 三次握手才可以同步雙方的初始序列號
  • 三次握手才可以避免資源浪費

原因一:避免歷史連接

我們來看看 RFC 793 指出的 TCP 連接使用三次握手的首要原因:

The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.

簡單來說,三次握手的首要原因是為了防止舊的重復連接初始化造成混亂。

我們考慮一個場景,客戶端先發送了 SYN(seq = 90)報文,然后客戶端宕機了,而且這個 SYN 報文還被網絡阻塞了,服務端并沒有收到,接著客戶端重啟后,又重新向服務端建立連接,發送了 SYN(seq = 100)報文(注意!不是重傳 SYN,重傳的 SYN 的序列號是一樣的)。

看看三次握手是如何阻止歷史連接的:


image.png

客戶端連續發送多次 SYN(都是同一個四元組)建立連接的報文,在網絡擁堵情況下:

  • 一個「舊 SYN 報文」比「最新的 SYN」 報文早到達了服務端,那么此時服務端就會回一個 SYN + ACK 報文給客戶端,此報文中的確認號是 91(90+1)。
  • 客戶端收到后,發現自己期望收到的確認號應該是 100 + 1,而不是 90 + 1,于是就會回 RST 報文。
  • 服務端收到 RST 報文后,就會釋放連接。
  • 后續最新的 SYN 抵達了服務端后,客戶端與服務端就可以正常的完成三次握手了。

上述中的「舊 SYN 報文」稱為歷史連接,TCP 使用三次握手建立連接的最主要原因就是防止「歷史連接」初始化了連接。

如果是兩次握手連接,就無法阻止歷史連接,那為什么 TCP 兩次握手為什么無法阻止歷史連接呢?

我先直接說結論,主要是因為在兩次握手的情況下,服務端沒有中間狀態給客戶端來阻止歷史連接,導致服務端可能建立一個歷史連接,造成資源浪費。

你想想,在兩次握手的情況下,服務端在收到 SYN 報文后,就進入 ESTABLISHED 狀態,意味著這時可以給對方發送數據,但是客戶端此時還沒有進入 ESTABLISHED 狀態,假設這次是歷史連接,客戶端判斷到此次連接為歷史連接,那么就會回 RST 報文來斷開連接,而服務端在第一次握手的時候就進入 ESTABLISHED 狀態,所以它可以發送數據的,但是它并不知道這個是歷史連接,它只有在收到 RST 報文后,才會斷開連接。

image.png

可以看到,如果采用兩次握手建立 TCP 連接的場景下,服務端在向客戶端發送數據前,并沒有阻止掉歷史連接,導致服務端建立了一個歷史連接,又白白發送了數據,妥妥地浪費了服務端的資源。

因此,要解決這種現象,最好就是在服務端發送數據前,也就是建立連接之前,要阻止掉歷史連接,這樣就不會造成資源浪費,而要實現這個功能,就需要三次握手。

所以,TCP 使用三次握手建立連接的最主要原因是防止「歷史連接」初始化了連接。

原因二:同步雙方初始序列號

TCP 協議的通信雙方, 都必須維護一個「序列號」, 序列號是可靠傳輸的一個關鍵因素,它的作用:

  • 接收方可以去除重復的數據;
  • 接收方可以根據數據包的序列號按序接收;
  • 可以標識發送出去的數據包中, 哪些是已經被對方收到的(通過 ACK 報文中的序列號知道);

可見,序列號在 TCP 連接中占據著非常重要的作用,所以當客戶端發送攜帶「初始序列號」的 SYN 報文的時候,需要服務端回一個 ACK 應答報文,表示客戶端的 SYN 報文已被服務端成功接收,那當服務端發送「初始序列號」給客戶端的時候,依然也要得到客戶端的應答回應,這樣一來一回,才能確保雙方的初始序列號能被可靠的同步。

image.png

四次握手其實也能夠可靠的同步雙方的初始化序號,但由于第二步和第三步可以優化成一步,所以就成了「三次握手」。

而兩次握手只保證了一方的初始序列號能被對方成功接收,沒辦法保證雙方的初始序列號都能被確認接收。

原因三:避免資源浪費

如果只有「兩次握手」,當客戶端發生的 SYN 報文在網絡中阻塞,客戶端沒有接收到 ACK 報文,就會重新發送 SYN ,由于沒有第三次握手,服務端不清楚客戶端是否收到了自己回復的 ACK 報文,所以服務端每收到一個 SYN 就只能先主動建立一個連接,這會造成什么情況呢?

如果客戶端發送的 SYN 報文在網絡中阻塞了,重復發送多次 SYN 報文,那么服務端在收到請求后就會建立多個冗余的無效鏈接,造成不必要的資源浪費。

image.png

即兩次握手會造成消息滯留情況下,服務端重復接受無用的連接請求 SYN 報文,而造成重復分配資源。

為什么斷開連接是四次揮手?
服務器收到客戶端的 FIN 報文時,內核會馬上回一個 ACK 應答報文,但是服務端應用程序可能還有數據要發送,所以并不能馬上發送 FIN 報文,而是將發送 FIN 報文的控制權交給服務端應用程序:

  • 如果服務端應用程序有數據要發送的話,就發完數據后,才調用關閉連接的函數;
  • 如果服務端應用程序沒有數據要發送的話,可以直接調用關閉連接的函數。

從上面過程可知,是否要發送第三次揮手的控制權不在內核,而是在被動關閉方的應用程序,因為應用程序可能還有數據要發送,由應用程序決定什么時候調用關閉連接的函數,當調用了關閉連接的函數,內核就會發送 FIN 報文了,所以服務端的 ACK 和 FIN 一般都會分開發送。

參考

小林coding
https://xiaolincoding.com

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。