阿里二面,面試官居然把 TCP 三次握手問的這麼細緻

肖邦linux發表於2021-03-27

TCP 的三次握手和四次揮手,可以說是老生常談的經典問題了,通常也作為各大公司常見的面試考題,具有一定的水平區分度。看似是簡單的面試問題,如果你的回答不符合面試官期待的水準,有可能就直接涼涼了。

本文會圍繞,三次握手和四次揮手相關的一些列核心問題,分享如何更準確的回答和應對常見的面試問題,以後面對再刁鑽的面試官,你都可以隨意地跟他扯皮了。

面試TCP的意義

我想要先說明一個重要問題,到底面試 TCP 的意義何在?

經常會聽到這樣抱怨:我是做業務程式開發的,面試官竟然問我 TCP 三次握手、TCP 擁塞控制的問題,還問的這麼細緻?

這些同學會覺著面試官是閒的淡疼,或是故意刁難候選人,更有同學認為面試官是為了防止自己技術退步拿來練手的,這種想法我也是醉了。

當然,不同人對此可能會有不同的想法,但我們技術人應該以積極的心態來理解和麵對這個問題,在我看來面試 TCP 有重要的意義:

1. 從面試官的角度,可以快速考察候選人對基礎知識的掌握程度,以及候選人對待技術的那種知其所以然的態度。

2. 從求職者的角度,即使工作內容中沒有直接用到 TCP 協議,但在遇到網路故障,除錯和分析問題時,熟悉 TCP 顯得十分重要,要不抓包都看不懂。

3. 從學習的角度,我們可以學習 TCP 的設計理念,比如 TCP 重傳、擁塞控制,以及如何在效能和原理之間做權衡和取捨的,舉一反三,將這些原理細節應用到我們平時的軟體設計上,也是一種思維上的學習成長。

4. 如果想要調整 TCP 引數來提升傳輸速度,可伺服器上相關的系統引數有幾十個,究竟該怎麼調整呢?

5. 在伺服器本地的 TCP 連線狀態出現了類似 fin_waittime_wait,該怎麼解決,是什麼原因引起的?如果不懂 TCP,即使別人告訴你解決方案,你也不能夠真正理解的。

所以,我們非常有必要認真學習 TCP 協議,對 TCP 熟悉程度,在某種意義上也是你與別人拉開距離的重要標識。

TCP 基礎

這裡先幫小夥伴們熟悉和回顧下 TCP 的基本概念,以至於能夠更好的理解文章後邊的內容。

TCP 其實是非常複雜的協議,我們先聊一些基礎的。我們知道 TCP 是一種可靠的協議,它主要通過解決這幾個問題來實現可靠性的,分別是:亂序、丟包重傳、流控、擁塞控制。通過從圖中報文格式的欄位,也能夠簡單瞭解到 TCP 的相關概念。

  • TCP 在網路 OSI 七層模型中的第四層,TCP 包是沒有 IP 地址的,但有源埠和目的埠,用來標識通訊的程式。
  • Sequence Number 是記錄包的序號,TCP 會按照報文位元組進行編號,它是用來解決包在網路中亂序的問題。
  • Acknowledgement Number 確認序列號,是用於向傳送方確認已經收到了哪些包,用來解決不丟包的問題。
  • Windows 也叫 Advertised-Windows,也就是著名的滑動視窗,主要是用來解決流控的。
  • TCP Flag 就是包的型別,主要是用於操控 TCP 狀態機的。

三次握手

三次握手是各個公司常見的面試考點。以過來人經驗來講,雖然該問題看似簡單,但你還真不一定能夠回答的好。

見過比較典型面試問答場景:

面試官:請描述一下三次握手的過程吧
求職者:第一次客戶端給服務端傳送一個報文,第二次是伺服器收到包之後,也給客戶端應答一個報文,第三次是客戶端再給伺服器傳送一個回覆報文,TCP 三次握手成功。
面試官:還有嗎?
求職者:說完了哈,這就是三次握手,很簡單的
面試官:嗯,我沒什麼問的了,你還有什麼問題嗎?

這時求職者緊張的心終於平靜了,因為面試官沒有深入下去的意思,繼續問下去可能也不懂,皆大歡喜!當然本次面試基本上也就 game over了。

求職者回答的不正確麼?正確,但是回答的過於簡單,離面試官的期望的答案還有一定的距離,我們該怎麼回答呢?

TCP 三次握手,其實就是建立一個 TCP 連線,客戶端與伺服器互動需要 3 個資料包。握手的主要作用就是為了確認雙方的接收和傳送能力是否正常,初始序列號,交換視窗大小以及 MSS 等資訊。

  • 第一次握手:客戶端傳送 SYN 報文,並進入 SYN_SENT 狀態,等待伺服器的確認;
  • 第二次握手:伺服器收到 SYN 報文,需要給客戶端傳送 ACK 確認報文,同時伺服器也要向客戶端傳送一個 SYN 報文,所以也就是向客戶端傳送 SYN + ACK 報文,此時伺服器進入 SYN_RCVD 狀態;
  • 第三次握手:客戶端收到 SYN + ACK 報文,向伺服器傳送確認包,客戶端進入 ESTABLISHED 狀態。待伺服器收到客戶端傳送的 ACK 包也會進入 ESTABLISHED 狀態,完成三次握手。

我們回答時,可以先簡單概述 TCP 過程,然後三次握手具體描述時,需要說明狀態的基本轉換。

TCP 三次握手,其實就是 TCP 應用在傳送資料前,通過 TCP 協議跟通訊對方協商好連線資訊,建立起TCP的連線關係。

我們需要知道,TCP 連線並非是在通訊裝置兩端之間建立訊號隧道,而本質上就是雙方各自維護所需的狀態狀態,以達到 TCP 連線的效果。所以 TCP 狀態機是 TCP 的核心內容,學習 TCP 一定要搞懂這些狀態機之間的轉換。

二次握手可以嗎

問:為什麼 TCP 採用三次握手,二次握手可以嗎?

我們可以從幾個方面來解釋:

(一)確認雙方的收發能力

TCP 建立連線之前,需要確認客戶端與伺服器雙方的收包和發包的能力。

1. 第一次握手:客戶端傳送網路包,服務端收到了。這樣服務端就能得出結論:客戶端的傳送能力、服務端的接收能力是正常的。

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

3. 第三次握手:客戶端發包,服務端收到了。這樣服務端就能得出結論:客戶端的接收、傳送能力正常,伺服器自己的傳送、接收能力也正常。

所以,只有三次握手才能確認雙方的接收與傳送能力是否正常。

(二)序列號可靠同步

如果是兩次握手,服務端無法確定客戶端是否已經接收到了自己傳送的初始序列號,如果第二次握手報文丟失,那麼客戶端就無法知道服務端的初始序列號,那 TCP 的可靠性就無從談起。

(三)阻止重複歷史連線的初始化

客戶端由於某種原因傳送了兩個不同序號的 SYN 包,我們知道網路環境是複雜的,舊的資料包有可能先到達伺服器。如果是兩次握手,伺服器收到舊的 SYN 就會立刻建立連線,那麼會造成網路異常。

如果是三次握手,伺服器需要回復 SYN+ACK 包,客戶端會對比應答的序號,如果發現是舊的報文,就會給伺服器發 RST 報文,直到正常的 SYN 到達伺服器後才正常建立連線。

所以三次握手才有足夠的上下文資訊來判斷當前連線是否是歷史連線。

(四)安全問題

我們知道 TCP 新建連線時,核心會為連線分配一系列的記憶體資源,如果採用兩次握手,就建立連線,那會放大 DDOS 攻擊的。

TCP 作為一種可靠傳輸控制協議,其核心思想:既要保證資料可靠傳輸,又要提高傳輸的效率,而三次握手恰好可以滿足以上兩方面的需求!

初始序列號(ISN)

問:ISN 代表什麼?意義何在?ISN 是固定不變的嗎?ISN為何要動態隨機?

ISN 是什麼?

答:ISN 全稱是 Initial Sequence Number,是 TCP 傳送方的位元組資料編號的原點,告訴對方我要開始傳送資料的初始化序列號

ISN 是固定不變的嗎?

答:ISN 如果是固定的,攻擊者很容易猜出後續的確認序號,為了安全起見,避免被第三方猜到從而傳送偽造的 RST 報文,因此 ISN 是動態生成的

半連線佇列

什麼是半連線佇列?

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

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

三次握手可以攜帶資料嗎?

問:三次握手過程中,可以攜帶資料嗎?

答:第一次、第二次握手不可以攜帶資料,而第三次握手是可以攜帶資料的。

我們可以思考一個問題,假如第一次握手可以攜帶資料的話,如果有人要惡意攻擊伺服器,那他每次都在第一次握手中的 SYN 報文中放入大量的資料,瘋狂著重複發 SYN 報文,這會讓伺服器花費大量的記憶體空間來快取這些報文,這樣伺服器就更容易被攻擊了。

對於第三次握手,此時客戶端已經處於連線狀態,他已經知道伺服器的接收、傳送能力是正常的了,所以可以攜帶資料是情理之中。

TCP 四次揮手

當我們的應用程式不需要資料通訊了,就會發起斷開 TCP 連線。建立一個連線需要三次握手,而終止一個連線需要經過四次揮手。

  • 第一次揮手。客戶端發起 FIN 包(FIN = 1),客戶端進入 FIN_WAIT_1 狀態。TCP 規定,即使 FIN 包不攜帶資料,也要消耗一個序號。
  • 第二次揮手。伺服器端收到 FIN 包,發出確認包 ACK(ack = u + 1),並帶上自己的序號 seq=v,伺服器端進入了 CLOSE_WAIT 狀態。這個時候客戶端已經沒有資料要傳送了,不過伺服器端有資料傳送的話,客戶端依然需要接收。客戶端接收到伺服器端傳送的 ACK 後,進入了 FIN_WAIT_2 狀態。
  • 第三次揮手。伺服器端資料傳送完畢後,向客戶端傳送 FIN 包(seq=w ack=u+1),半連線狀態下伺服器可能又傳送了一些資料,假設傳送 seq 為 w。伺服器此時進入了 LAST_ACK 狀態。
  • 第四次揮手。客戶端收到伺服器的 FIN 包後,發出確認包(ACK=1,ack=w+1),此時客戶端就進入了 TIME_WAIT 狀態。注意此時 TCP 連線還沒有釋放,必須經過 2*MSL 後,才進入 CLOSED 狀態。而伺服器端收到客戶端的確認包 ACK 後就進入了 CLOSED 狀態,可以看出伺服器端結束 TCP 連線的時間要比客戶端早一些。

問:為什麼建立連線握手三次,關閉連線時需要是四次呢?

答:其實在 TCP 握手的時候,接收端傳送 SYN+ACK 的包是將一個 ACK 和一個 SYN 合併到一個包中,所以減少了一次包的傳送,三次完成握手。

對於四次揮手,因為 TCP 是全雙工通訊,在主動關閉方傳送 FIN 包後,接收端可能還要傳送資料,不能立即關閉伺服器端到客戶端的資料通道,所以也就不能將伺服器端的 FIN 包與對客戶端的 ACK 包合併傳送,只能先確認 ACK,然後伺服器待無需傳送資料時再傳送 FIN 包,所以四次揮手時必須是四次資料包的互動。

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

答:MSL 指的是報文在網路中最大生存時間。在客戶端傳送對伺服器端的 FIN 的確認包 ACK 後,這個 ACK 包是有可能不可達的,伺服器端如果收不到 ACK 的話需要重新傳送 FIN 包。

所以客戶端傳送 ACK 後需要留出 2MSL 時間(ACK 到達伺服器 + 伺服器傳送 FIN 重傳包,一來一回)等待確認伺服器端確實收到了 ACK 包。

也就是說客戶端如果等待 2MSL 時間也沒有收到伺服器端的重傳包 FIN,說明可以確認伺服器已經收到客戶端傳送的 ACK

還有第 2 個理由,避免新舊連線混淆。

在客戶端傳送完最後一個 ACK 報文段後,在經過 2MSL 時間,就可以使本連線持續的時間內所產生的所有報文都從網路中消失,使下一個新的連線中不會出現這種舊的連線請求報文。

你要知道,有些自作主張的路由器會快取 IP 資料包,如果連線重用了,那麼這些延遲收到的包就有可能會跟新連線混在一起。

總結

本篇文章以 TCP 三次握手和四次揮手這個經典問題為主題,初步窺探了 TCP 協議的入門知識點,後邊會有一系列的文章,來分享 TCP 協議相關的方方面面,如果感興趣請關注我,我們一起把 TCP 協議徹底搞透徹了。

最後,幫大家總結一下 TCP 的核心知識點。我們知道 TCP 協議是可靠的,它主要是通過解決如下幾個問題來保證可靠性的:

  • 亂序
  • 丟包
  • 流控
  • 擁塞控制

TCP 是一個巨複雜的協議,基本上 TCP 涉及的所有內容都是圍繞解決這幾個問題的,請務必時刻認真牢記。

推薦閱讀

相關文章