面試官,不要再問我三次握手和四次揮手

ciscopuke發表於2021-09-09

三次握手和四次揮手是各個公司常見的考點,也具有一定的水平區分度,也被一些面試官作為熱身題。很多小夥伴說這個問題剛開始回答的挺好,但是後面越回答越冒冷汗,最後就歇菜了。

見過比較典型的面試場景是這樣的:

面試官:請介紹下三次握手
求職者:第一次握手就是客戶端給伺服器端傳送一個報文,第二次就是伺服器收到報文之後,會應答一個報文給客戶端,第三次握手就是客戶端收到報文後再給伺服器傳送一個報文,三次握手就成功了。
面試官:然後呢?
求職者:這就是三次握手的過程,很簡單的。
面試官:。。。。。。
(番外篇:一首涼涼送給你)

記住猿人谷一句話:面試時越簡單的問題,一般就是隱藏著比較大的坑,一般都是需要將問題擴充套件的。上面求職者的回答不對嗎?當然對,但距離面試官的期望可能還有點距離。

希望大家能帶著如下問題進行閱讀,收穫會更大。

  1. 請畫出三次握手和四次揮手的示意圖
  2. 為什麼連線的時候是三次握手?
  3. 什麼是半連線佇列?
  4. ISN(Initial Sequence Number)是固定的嗎?
  5. 三次握手過程中可以攜帶資料嗎?
  6. 如果第三次握手丟失了,客戶端服務端會如何處理?
  7. SYN攻擊是什麼?
  8. 揮手為什麼需要四次?
  9. 四次揮手釋放連線時,等待2MSL的意義?

三次握手和四次揮手.png

1. 三次握手

三次握手(Three-way Handshake)其實就是指建立一個TCP連線時,需要客戶端和伺服器總共傳送3個包。進行三次握手的主要作用就是為了確認雙方的接收能力和傳送能力是否正常、指定自己的初始化序列號為後面的可靠性傳送做準備。實質上其實就是連線伺服器指定埠,建立TCP連線,並同步連線雙方的序列號和確認號,交換TCP視窗大小資訊。

剛開始客戶端處於 Closed 的狀態,服務端處於 Listen 狀態。
進行三次握手:

  • 第一次握手:客戶端給服務端發一個 SYN 報文,並指明客戶端的初始化序列號 ISN(c)。此時客戶端處於 SYN_SEND 狀態。

    首部的同步位SYN=1,初始序號seq=x,SYN=1的報文段不能攜帶資料,但要消耗掉一個序號。

  • 第二次握手:伺服器收到客戶端的 SYN 報文之後,會以自己的 SYN 報文作為應答,並且也是指定了自己的初始化序列號 ISN(s)。同時會把客戶端的 ISN + 1 作為ACK 的值,表示自己已經收到了客戶端的 SYN,此時伺服器處於 SYN_REVD 的狀態。

    在確認報文段中SYN=1,ACK=1,確認號ack=x+1,初始序號seq=y。

  • 第三次握手:客戶端收到 SYN 報文之後,會傳送一個 ACK 報文,當然,也是一樣把伺服器的 ISN + 1 作為 ACK 的值,表示已經收到了服務端的 SYN 報文,此時客戶端處於 ESTABLISHED 狀態。伺服器收到 ACK 報文之後,也處於 ESTABLISHED 狀態,此時,雙方已建立起了連線。

    確認報文段ACK=1,確認號ack=y+1,序號seq=x+1(初始為seq=x,第二個報文段所以要+1),ACK報文段可以攜帶資料,不攜帶資料則不消耗序號。

傳送第一個SYN的一端將執行主動開啟(active open),接收這個SYN併發回下一個SYN的另一端執行被動開啟(passive open)。

在socket程式設計中,客戶端執行connect()時,將觸發三次握手。

三次握手.png

1.1 為什麼需要三次握手,兩次不行嗎?

弄清這個問題,我們需要先弄明白三次握手的目的是什麼,能不能只用兩次握手來達到同樣的目的。

  • 第一次握手:客戶端傳送網路包,服務端收到了。
    這樣服務端就能得出結論:客戶端的傳送能力、服務端的接收能力是正常的。
  • 第二次握手:服務端發包,客戶端收到了。
    這樣客戶端就能得出結論:服務端的接收、傳送能力,客戶端的接收、傳送能力是正常的。不過此時伺服器並不能確認客戶端的接收能力是否正常。
  • 第三次握手:客戶端發包,服務端收到了。
    這樣服務端就能得出結論:客戶端的接收、傳送能力正常,伺服器自己的傳送、接收能力也正常。

因此,需要三次握手才能確認雙方的接收與傳送能力是否正常。

試想如果是用兩次握手,則會出現下面這種情況:

如客戶端發出連線請求,但因連線請求報文丟失而未收到確認,於是客戶端再重傳一次連線請求。後來收到了確認,建立了連線。資料傳輸完畢後,就釋放了連線,客戶端共發出了兩個連線請求報文段,其中第一個丟失,第二個到達了服務端,但是第一個丟失的報文段只是在某些網路結點長時間滯留了,延誤到連線釋放以後的某個時間才到達服務端,此時服務端誤認為客戶端又發出一次新的連線請求,於是就向客戶端發出確認報文段,同意建立連線,不採用三次握手,只要服務端發出確認,就建立新的連線了,此時客戶端忽略服務端發來的確認,也不傳送資料,則服務端一致等待客戶端傳送資料,浪費資源。

1.2 什麼是半連線佇列?

伺服器第一次收到客戶端的 SYN 之後,就會處於 SYN_RCVD 狀態,此時雙方還沒有完全建立其連線,伺服器會把此種狀態下請求連線放在一個佇列裡,我們把這種佇列稱之為半連線佇列

當然還有一個全連線佇列,就是已經完成三次握手,建立起連線的就會放在全連線佇列中。如果佇列滿了就有可能會出現丟包現象。

這裡在補充一點關於SYN-ACK 重傳次數的問題:
伺服器傳送完SYN-ACK包,如果未收到客戶確認包,伺服器進行首次重傳,等待一段時間仍未收到客戶確認包,進行第二次重傳。如果重傳次數超過系統規定的最大重傳次數,系統將該連線資訊從半連線佇列中刪除。
注意,每次重傳等待的時間不一定相同,一般會是指數增長,例如間隔時間為 1s,2s,4s,8s......

1.3 ISN(Initial Sequence Number)是固定的嗎?

當一端為建立連線而傳送它的SYN時,它為連線選擇一個初始序號。ISN隨時間而變化,因此每個連線都將具有不同的ISN。ISN可以看作是一個32位元的計數器,每4ms加1 。這樣選擇序號的目的在於防止在網路中被延遲的分組在以後又被傳送,而導致某個連線的一方對它做錯誤的解釋。

三次握手的其中一個重要功能是客戶端和服務端交換 ISN(Initial Sequence Number),以便讓對方知道接下來接收資料的時候如何按序列號組裝資料。如果 ISN 是固定的,攻擊者很容易猜出後續的確認號,因此 ISN 是動態生成的。

1.4 三次握手過程中可以攜帶資料嗎?

其實第三次握手的時候,是可以攜帶資料的。但是,第一次、第二次握手不可以攜帶資料

為什麼這樣呢?大家可以想一個問題,假如第一次握手可以攜帶資料的話,如果有人要惡意攻擊伺服器,那他每次都在第一次握手中的 SYN 報文中放入大量的資料。因為攻擊者根本就不理伺服器的接收、傳送能力是否正常,然後瘋狂著重複發 SYN 報文的話,這會讓伺服器花費很多時間、記憶體空間來接收這些報文。

也就是說,第一次握手不可以放資料,其中一個簡單的原因就是會讓伺服器更加容易受到攻擊了。而對於第三次的話,此時客戶端已經處於 ESTABLISHED 狀態。對於客戶端來說,他已經建立起連線了,並且也已經知道伺服器的接收、傳送能力是正常的了,所以能攜帶資料也沒啥毛病。

1.5 SYN攻擊是什麼?

伺服器端的資源分配是在二次握手時分配的,而客戶端的資源是在完成三次握手時分配的,所以伺服器容易受到SYN洪泛攻擊。SYN攻擊就是Client在短時間內偽造大量不存在的IP地址,並向Server不斷地傳送SYN包,Server則回覆確認包,並等待Client確認,由於源地址不存在,因此Server需要不斷重發直至超時,這些偽造的SYN包將長時間佔用未連線佇列,導致正常的SYN請求因為佇列滿而被丟棄,從而引起網路擁塞甚至系統癱瘓。SYN 攻擊是一種典型的 DoS/DDoS 攻擊。

檢測 SYN 攻擊非常的方便,當你在伺服器上看到大量的半連線狀態時,特別是源IP地址是隨機的,基本上可以斷定這是一次SYN攻擊。在 Linux/Unix 上可以使用系統自帶的 netstats 命令來檢測 SYN 攻擊。

netstat -n -p TCP | grep SYN_RECV

常見的防禦 SYN 攻擊的方法有如下幾種:

  • 縮短超時(SYN Timeout)時間
  • 增加最大半連線數
  • 過濾閘道器防護
  • SYN cookies技術

2. 四次揮手

建立一個連線需要三次握手,而終止一個連線要經過四次揮手(也有將四次揮手叫做四次握手的)。這由TCP的半關閉(half-close)造成的。所謂的半關閉,其實就是TCP提供了連線的一端在結束它的傳送後還能接收來自另一端資料的能力。

TCP 的連線的拆除需要傳送四個包,因此稱為四次揮手(Four-way handshake),客戶端或伺服器均可主動發起揮手動作。

剛開始雙方都處於 ESTABLISHED 狀態,假如是客戶端先發起關閉請求。四次揮手的過程如下:

  • 第一次揮手:客戶端傳送一個 FIN 報文,報文中會指定一個序列號。此時客戶端處於 FIN_WAIT1 狀態。
    即發出連線釋放報文段(FIN=1,序號seq=u),並停止再傳送資料,主動關閉TCP連線,進入FIN_WAIT1(終止等待1)狀態,等待服務端的確認。
  • 第二次揮手:服務端收到 FIN 之後,會傳送 ACK 報文,且把客戶端的序列號值 +1 作為 ACK 報文的序列號值,表明已經收到客戶端的報文了,此時服務端處於 CLOSE_WAIT 狀態。
    即服務端收到連線釋放報文段後即發出確認報文段(ACK=1,確認號ack=u+1,序號seq=v),服務端進入CLOSE_WAIT(關閉等待)狀態,此時的TCP處於半關閉狀態,客戶端到服務端的連線釋放。客戶端收到服務端的確認後,進入FIN_WAIT2(終止等待2)狀態,等待服務端發出的連線釋放報文段。
  • 第三次揮手:如果服務端也想斷開連線了,和客戶端的第一次揮手一樣,發給 FIN 報文,且指定一個序列號。此時服務端處於 LAST_ACK 的狀態。
    即服務端沒有要向客戶端發出的資料,服務端發出連線釋放報文段(FIN=1,ACK=1,序號seq=w,確認號ack=u+1),服務端進入LAST_ACK(最後確認)狀態,等待客戶端的確認。
  • 第四次揮手:客戶端收到 FIN 之後,一樣傳送一個 ACK 報文作為應答,且把服務端的序列號值 +1 作為自己 ACK 報文的序列號值,此時客戶端處於 TIME_WAIT 狀態。需要過一陣子以確保服務端收到自己的 ACK 報文之後才會進入 CLOSED 狀態,服務端收到 ACK 報文之後,就處於關閉連線了,處於 CLOSED 狀態。
    即客戶端收到服務端的連線釋放報文段後,對此發出確認報文段(ACK=1,seq=u+1,ack=w+1),客戶端進入TIME_WAIT(時間等待)狀態。此時TCP未釋放掉,需要經過時間等待計時器設定的時間2MSL後,客戶端才進入CLOSED狀態。

收到一個FIN只意味著在這一方向上沒有資料流動。客戶端執行主動關閉並進入TIME_WAIT是正常的,服務端通常執行被動關閉,不會進入TIME_WAIT狀態。

在socket程式設計中,任何一方執行close()操作即可產生揮手操作。
image.png

2.1 揮手為什麼需要四次?

因為當服務端收到客戶端的SYN連線請求報文後,可以直接傳送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連線時,當服務端收到FIN報文時,很可能並不會立即關閉SOCKET,所以只能先回復一個ACK報文,告訴客戶端,"你發的FIN報文我收到了"。只有等到我服務端所有的報文都傳送完了,我才能傳送FIN報文,因此不能一起傳送。故需要四次揮手。

2.2 2MSL等待狀態

TIME_WAIT狀態也成為2MSL等待狀態。每個具體TCP實現必須選擇一個報文段最大生存時間MSL(Maximum Segment Lifetime),它是任何報文段被丟棄前在網路內的最長時間。這個時間是有限的,因為TCP報文段以IP資料包在網路內傳輸,而IP資料包則有限制其生存時間的TTL欄位。

對一個具體實現所給定的MSL值,處理的原則是:當TCP執行一個主動關閉,併發回最後一個ACK,該連線必須在TIME_WAIT狀態停留的時間為2倍的MSL。這樣可讓TCP再次傳送最後的ACK以防這個ACK丟失(另一端超時並重發最後的FIN)。

這種2MSL等待的另一個結果是這個TCP連線在2MSL等待期間,定義這個連線的插口(客戶的IP地址和埠號,伺服器的IP地址和埠號)不能再被使用。這個連線只能在2MSL結束後才能再被使用。

2.3 四次揮手釋放連線時,等待2MSL的意義?

MSL是Maximum Segment Lifetime的英文縮寫,可譯為“最長報文段壽命”,它是任何報文在網路上存在的最長時間,超過這個時間報文將被丟棄。

為了保證客戶端傳送的最後一個ACK報文段能夠到達伺服器。因為這個ACK有可能丟失,從而導致處在LAST-ACK狀態的伺服器收不到對FIN-ACK的確認報文。伺服器會超時重傳這個FIN-ACK,接著客戶端再重傳一次確認,重新啟動時間等待計時器。最後客戶端和伺服器都能正常的關閉。假設客戶端不等待2MSL,而是在傳送完ACK之後直接釋放關閉,一但這個ACK丟失的話,伺服器就無法正常的進入關閉連線狀態。

兩個理由:

  1. 保證客戶端傳送的最後一個ACK報文段能夠到達服務端。
    這個ACK報文段有可能丟失,使得處於LAST-ACK狀態的B收不到對已傳送的FIN+ACK報文段的確認,服務端超時重傳FIN+ACK報文段,而客戶端能在2MSL時間內收到這個重傳的FIN+ACK報文段,接著客戶端重傳一次確認,重新啟動2MSL計時器,最後客戶端和服務端都進入到CLOSED狀態,若客戶端在TIME-WAIT狀態不等待一段時間,而是傳送完ACK報文段後立即釋放連線,則無法收到服務端重傳的FIN+ACK報文段,所以不會再傳送一次確認報文段,則服務端無法正常進入到CLOSED狀態。
  2. 防止“已失效的連線請求報文段”出現在本連線中。
    客戶端在傳送完最後一個ACK報文段後,再經過2MSL,就可以使本連線持續的時間內所產生的所有報文段都從網路中消失,使下一個新的連線中不會出現這種舊的連線請求報文段。

2.4 為什麼TIME_WAIT狀態需要經過2MSL才能返回到CLOSE狀態?

理論上,四個報文都傳送完畢,就可以直接進入CLOSE狀態了,但是可能網路是不可靠的,有可能最後一個ACK丟失。所以TIME_WAIT狀態就是用來重發可能丟失的ACK報文

3. 總結

《TCP/IP詳解 卷1:協議》有一張TCP狀態變遷圖,很具有代表性,有助於大家理解三次握手和四次揮手的狀態變化。如下圖所示,粗的實線箭頭表示正常的客戶端狀態變遷,粗的虛線箭頭表示正常的伺服器狀態變遷。

TCP狀態變遷圖.jpg

以後面試官再問你三次握手和四次揮手,直接把這一篇文章丟給他就可以了,他想問的都在這裡。

參考:《TCP/IP詳解 卷1:協議》

相關文章