tcp的三次握手過程說一下:
一開始,客戶端和服務端都處于 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的四次揮手的各個狀態說一下:
具體過程:
客戶端主動調用關閉連接的函數,于是就會發送 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 的序列號是一樣的)。
看看三次握手是如何阻止歷史連接的:
客戶端連續發送多次 SYN(都是同一個四元組)建立連接的報文,在網絡擁堵情況下:
- 一個「舊 SYN 報文」比「最新的 SYN」 報文早到達了服務端,那么此時服務端就會回一個 SYN + ACK 報文給客戶端,此報文中的確認號是 91(90+1)。
- 客戶端收到后,發現自己期望收到的確認號應該是 100 + 1,而不是 90 + 1,于是就會回 RST 報文。
- 服務端收到 RST 報文后,就會釋放連接。
- 后續最新的 SYN 抵達了服務端后,客戶端與服務端就可以正常的完成三次握手了。
上述中的「舊 SYN 報文」稱為歷史連接,TCP 使用三次握手建立連接的最主要原因就是防止「歷史連接」初始化了連接。
如果是兩次握手連接,就無法阻止歷史連接,那為什么 TCP 兩次握手為什么無法阻止歷史連接呢?
我先直接說結論,主要是因為在兩次握手的情況下,服務端沒有中間狀態給客戶端來阻止歷史連接,導致服務端可能建立一個歷史連接,造成資源浪費。
你想想,在兩次握手的情況下,服務端在收到 SYN 報文后,就進入 ESTABLISHED 狀態,意味著這時可以給對方發送數據,但是客戶端此時還沒有進入 ESTABLISHED 狀態,假設這次是歷史連接,客戶端判斷到此次連接為歷史連接,那么就會回 RST 報文來斷開連接,而服務端在第一次握手的時候就進入 ESTABLISHED 狀態,所以它可以發送數據的,但是它并不知道這個是歷史連接,它只有在收到 RST 報文后,才會斷開連接。
可以看到,如果采用兩次握手建立 TCP 連接的場景下,服務端在向客戶端發送數據前,并沒有阻止掉歷史連接,導致服務端建立了一個歷史連接,又白白發送了數據,妥妥地浪費了服務端的資源。
因此,要解決這種現象,最好就是在服務端發送數據前,也就是建立連接之前,要阻止掉歷史連接,這樣就不會造成資源浪費,而要實現這個功能,就需要三次握手。
所以,TCP 使用三次握手建立連接的最主要原因是防止「歷史連接」初始化了連接。
原因二:同步雙方初始序列號
TCP 協議的通信雙方, 都必須維護一個「序列號」, 序列號是可靠傳輸的一個關鍵因素,它的作用:
- 接收方可以去除重復的數據;
- 接收方可以根據數據包的序列號按序接收;
- 可以標識發送出去的數據包中, 哪些是已經被對方收到的(通過 ACK 報文中的序列號知道);
可見,序列號在 TCP 連接中占據著非常重要的作用,所以當客戶端發送攜帶「初始序列號」的 SYN 報文的時候,需要服務端回一個 ACK 應答報文,表示客戶端的 SYN 報文已被服務端成功接收,那當服務端發送「初始序列號」給客戶端的時候,依然也要得到客戶端的應答回應,這樣一來一回,才能確保雙方的初始序列號能被可靠的同步。
四次握手其實也能夠可靠的同步雙方的初始化序號,但由于第二步和第三步可以優化成一步,所以就成了「三次握手」。
而兩次握手只保證了一方的初始序列號能被對方成功接收,沒辦法保證雙方的初始序列號都能被確認接收。
原因三:避免資源浪費
如果只有「兩次握手」,當客戶端發生的 SYN 報文在網絡中阻塞,客戶端沒有接收到 ACK 報文,就會重新發送 SYN ,由于沒有第三次握手,服務端不清楚客戶端是否收到了自己回復的 ACK 報文,所以服務端每收到一個 SYN 就只能先主動建立一個連接,這會造成什么情況呢?
如果客戶端發送的 SYN 報文在網絡中阻塞了,重復發送多次 SYN 報文,那么服務端在收到請求后就會建立多個冗余的無效鏈接,造成不必要的資源浪費。
即兩次握手會造成消息滯留情況下,服務端重復接受無用的連接請求 SYN 報文,而造成重復分配資源。
為什么斷開連接是四次揮手?
服務器收到客戶端的 FIN 報文時,內核會馬上回一個 ACK 應答報文,但是服務端應用程序可能還有數據要發送,所以并不能馬上發送 FIN 報文,而是將發送 FIN 報文的控制權交給服務端應用程序:
- 如果服務端應用程序有數據要發送的話,就發完數據后,才調用關閉連接的函數;
- 如果服務端應用程序沒有數據要發送的話,可以直接調用關閉連接的函數。
從上面過程可知,是否要發送第三次揮手的控制權不在內核,而是在被動關閉方的應用程序,因為應用程序可能還有數據要發送,由應用程序決定什么時候調用關閉連接的函數,當調用了關閉連接的函數,內核就會發送 FIN 報文了,所以服務端的 ACK 和 FIN 一般都會分開發送。
參考
小林coding
https://xiaolincoding.com