面試通關手冊(TCP三次握手和四次揮手)

TCP是一種面向連接的、可靠的、基於字節流的傳輸層通信協議,在發送數據前,通信雙方必須在彼此間建立一條連接。所謂的“連接”,其實是客戶端和服務端保存的一份關於對方的信息,如ip地址、端口號等。

一個TCP連接通常分為三個階段:連接、數據傳輸、退出(關閉)。通過三次握手建立一個鏈接,通過四次揮手來關閉一個連接。

1.TCP報文的頭部結構

在瞭解TCP連接之前先來了解一下TCP報文的頭部結構。

面試通關手冊(TCP三次握手和四次揮手)


1.1 端口號(16位)

源端口號標示這段報文來自哪裡;目的端口號標示這段報文要發往哪裡。進行tcp通信時,一般客戶端是通過系統自動選擇的臨時端口號,而服務器一般是使用指定的端口號。

1.2 序列號seq(32位)

因為TCP使用IP來傳輸報文段,而IP不能過濾掉重複的報文,也不能保證報文的順序。比如客戶端給服務端發送一條5kb的數據,如果tcp一次只能發1kb,那就要將數據分成5段分5次來發,將這5段數據分別編號為1、2、3、4、5,發送的時候按照這個順序進行發送,服務端收到的順序可能就是13254,也就是說服務端收到的順序和發送的順序可能是不一致的,甚至因為某些因素(比如客戶端沒收到服務端回覆的ACK數據包就會重發數據)導致服務端收到多個編號相同的數據。

那這樣的話服務端收到全部數據後要如何將它們拼接成正確的數據呢?這裡就用到了序列號。首先序列號被系統初始化為一個隨機值ISN,一個報文的序列號就是ISN+這個報文攜帶的數據的第一個字節的偏移量。比如上面所說的例子要發5個報文,第一個報文的第一個字節的偏移量為0,序列號就是ISA+0;由於第一個報文攜帶的數據大小是1kb(1024),所以第二個報文的第一個字節的偏移量就是1024,序列號就是ISA+1024,以此類推。(注意實際上三次握手時是會佔據一個序列號的,所以實際上正式發送數據時第一個報文的序列號是ISA+1+0,這裡為了方便理解就不考慮三次握手時佔據的那個序列號)。

1.3 確認號ack(32位)

還是用前面的例子,服務端收到客戶端發過來的報文後需要給客戶端回覆一個ack數據包,回覆報文的確認號的值等於服務端收到的報文的序列號的值+1,比如服務端當前收到的報文的序列號是ISN+2048(也就是第3段數據),那麼它回覆客戶端的報文的確認號的值就是ISN+2048+1,其作用就是告訴客戶端ISN+2048+1之前的所有數據都已經收到了。

關於ack回覆有幾點需要說明(下面客戶端為發送端,服務端為接收端):

a.客戶端發送一個報文後並不需要等服務端的ack回覆就可以接著發下一條報文。

b.服務端回覆ack時,必須要確保確認號之前的數據全部已經收到了,比如上面的例子,如果序列號是ISN+2048的報文收到了,但是ISN+1024的報文還沒收到,那就不能回覆ISN+2048+1的ack。

c.服務端在收到數據後不是立即給客戶端發送ack的,一般會有200ms的延遲(系統有個定時器每隔200ms來檢查是否需要發送ack包)。這麼做是因為TCP數據包到達的順序是不保證的,就比如上面必須要等ISN+1024的數據收到了才能回覆ISN+2048+1的ack,這個時候就只用回覆ISN+2048+1這一個ack就可以了,不需要回復ISN+1024+1的ack了,因為回覆ISN+2048+1的ack就已經告訴客戶端ISN+2048+1之前的數據已經全部收到了,這樣做還可以減少網絡流量。當然如果ISN+1024的數據丟包導致服務端一直沒收到,那客戶端也就一直收不到ack回覆,客戶端就會從上次收到的ack回覆開始重發數據,包括服務端已經收到的ISN+2048數據也會重發。另外如果服務端剛好也有數據要發給客戶端,那麼就會在發送數據的TCP數據包裡帶上ack信息。

1.4 頭部長度(4位)

頭部長度是以32bit(4字節)為一個單位,頭部長度的值表示TCP頭部總共多少個32bit,4位的最大是二進制1111(十進制15),TCP頭部最大為15*32/8個字節,也就是60字節。TCP頭部前面20個字節是固定的,TCP頭部最小也就是20個字節。

1.5 六個標誌位(每個標誌佔1位,每個標誌只有0和1兩種狀態)

URG:為1時表示緊急指針有效。

ACK:為1時表示確認號是有效的,攜帶ACK標誌的報文段也稱確認報文段。

PSH:為1時是提示接收端應用程序應該立即從TCP接受緩衝區中讀走數據,為後續接收的數據讓出空間。

RST:為1時表示通知對方關閉連接或重新建立連接。帶RST標誌的TCP報文段也叫復位報文段。很多異常情況都會導致無法建立TCP連接或者TCP連接異常終止,比如客戶端請求中使用了一個不存在的端口,那服務端就可以發送RST報文段拒絕這個請求;再比如TCP連接很久沒有傳輸數據了,可以發送RST報文段來終止這個連接;還比如TCP連接出現了一次,然後服務端希望終止這條異常的鏈接,可以發送RST報文段來終止這個連接。注意一旦發送了復位報文段,發送端所有排隊等待發送的數據都將被丟棄,而且發送完RST報文後TCP連接就關閉了,所以接收端收到RST報文後也就沒有必要發送ACK包來確認了。

SYN:為1表示建立一個連接,攜帶SYN標誌的報文段為同步報文段。SYN標誌位只有在TCP建立連接時(也就是三次握手的時候)才會被置為1,客戶端請求建立連接時(第一次握手)的報文就攜帶SYN標誌和初始化的序列號(也就是起始序列號),SYN標誌是提醒服務端記住客戶端的起始序列號。然後服務端也會初始化自己的起始序列號並回復客戶端一條報文(第二次握手),這條報文包含服務端的起始序列號、SYN標誌位和ACK標誌位,其中SYN標誌位用來提醒客戶端記住服務端的起始序列號。

FIN:為1時用來告知對方本端要關閉連接了。

1.6 窗口大小(16位)

窗口大小可以理解為自己接收數據的緩存區大小,用來告訴對方自己最大還能接收多少數據,如果對方發送的數據超過了窗口大小,那這個數據會被丟棄。當窗口大小為0時對方會暫停發送數據,直到窗口大小大於0時才會恢復發送數據。

1.7 校驗和(16位)

發送端對TCP報文(包括TCP頭部和數據部分)執行CRC算法得到校驗和,接收端收到報文後對報文執行同樣的算法,將結果與校驗和進行比對,兩者一致說明TCP報文段在傳輸過程中沒有損壞,如果不一致那麼這段TCP報文段會被直接丟棄。

1.8 緊急指針(16位)

只有當URG標誌位為1時緊急指針才是有意義的,緊急指針指向緊急數據最後一個字節的下一位。準確來說緊急指針的值其實是一個相對於序列號的偏移量,比如說緊急指針的值是5,如果這個報文段的序列號seq=10,那就表示緊急指針指向的是15這個位置的字節,這就意味著10到14這段數據是緊急數據。緊急數據是不進入緩存區的。(注:關於緊急指針的指向和緊急數據的長度網上有不同的說法,我這裡也不能確定哪種是正確的)。

1.9 TCP頭部選項

TCP頭部選項時長度可變的可選信息,最多包含40個字節。典型的TCP頭部選項包含kind、length和info三個部分,kind表示選項的類型,length表示選項的長度,info表示選項的具體內容。

2. 三次握手


面試通關手冊(TCP三次握手和四次揮手)


  • 第一次握手:客戶端要向服務端發起連接請求,首先客戶端隨機生成一個起始序列號ISN(比如是100),那客戶端向服務端發送的報文段包含SYN標誌位(也就是SYN=1),序列號seq=100。
  • 第二次握手:服務端收到客戶端發過來的報文後,發現SYN=1,知道這是一個連接請求,於是將客戶端的起始序列號100存起來,並且隨機生成一個服務端的起始序列號(比如是300)。然後給客戶端回覆一段報文,回覆報文包含SYN和ACK標誌(也就是SYN=1,ACK=1)、序列號seq=300、確認號ack=101(客戶端發過來的序列號+1)。
  • 第三次握手:客戶端收到服務端的回覆後發現ACK=1並且ack=101,於是知道服務端已經收到了序列號為100的那段報文;同時發現SYN=1,知道了服務端同意了這次連接,於是就將服務端的序列號300給存下來。然後客戶端再回復一段報文給服務端,報文包含ACK標誌位(ACK=1)、ack=301(服務端序列號+1)、seq=101(第一次握手時發送報文是佔據一個序列號的,所以這次seq就從101開始,需要注意的是不攜帶數據的ACK報文是不佔據序列號的,所以後面第一次正式發送數據時seq還是101)。當服務端收到報文後發現ACK=1並且ack=301,就知道客戶端收到序列號為300的報文了,就這樣客戶端和服務端通過TCP建立了連接。

3. 四次揮手


面試通關手冊(TCP三次握手和四次揮手)

比如客戶端初始化的序列號ISA=100,服務端初始化的序列號ISA=300。TCP連接成功後客戶端總共發送了1000個字節的數據,服務端在客戶端發FIN報文前總共回覆了2000個字節的數據。


  • 第一次揮手:當客戶端的數據都傳輸完成後,客戶端向服務端發出連接釋放報文(當然數據沒發完時也可以發送連接釋放報文並停止發送數據),釋放連接報文包含FIN標誌位(FIN=1)、序列號seq=1101(100+1+1000,其中的1是建立連接時佔的一個序列號)。需要注意的是客戶端發出FIN報文段後只是不能發數據了,但是還可以正常收數據;另外FIN報文段即使不攜帶數據也要佔據一個序列號。
  • 第二次揮手:服務端收到客戶端發的FIN報文後給客戶端回覆確認報文,確認報文包含ACK標誌位(ACK=1)、確認號ack=1102(客戶端FIN報文序列號1101+1)、序列號seq=2300(300+2000)。此時服務端處於關閉等待狀態,而不是立馬給客戶端發FIN報文,這個狀態還要持續一段時間,因為服務端可能還有數據沒發完。
  • 第三次揮手:服務端將最後數據(比如50個字節)發送完畢後就向客戶端發出連接釋放報文,報文包含FIN和ACK標誌位(FIN=1,ACK=1)、確認號和第二次揮手一樣ack=1102、序列號seq=2350(2300+50)。
  • 第四次揮手:客戶端收到服務端發的FIN報文後,向服務端發出確認報文,確認報文包含ACK標誌位(ACK=1)、確認號ack=2351、序列號seq=1102。注意客戶端發出確認報文後不是立馬釋放TCP連接,而是要經過2MSL(最長報文段壽命的2倍時長)後才釋放TCP連接。而服務端一旦收到客戶端發出的確認報文就會立馬釋放TCP連接,所以服務端結束TCP連接的時間要比客戶端早一些。

4. 常見面試題

4.1 為什麼TCP連接的時候是3次?2次不可以嗎?

因為需要考慮連接時丟包的問題,如果只握手2次,第二次握手時如果服務端發給客戶端的確認報文段丟失,此時服務端已經準備好了收發數(可以理解服務端已經連接成功)據,而客戶端一直沒收到服務端的確認報文,所以客戶端就不知道服務端是否已經準備好了(可以理解為客戶端未連接成功),這種情況下客戶端不會給服務端發數據,也會忽略服務端發過來的數據。

如果是三次握手,即便發生丟包也不會有問題,比如如果第三次握手客戶端發的確認ack報文丟失,服務端在一段時間內沒有收到確認ack報文的話就會重新進行第二次握手,也就是服務端會重發SYN報文段,客戶端收到重發的報文段後會再次給服務端發送確認ack報文。

4.2 為什麼TCP連接的時候是3次,關閉的時候卻是4次?

因為只有在客戶端和服務端都沒有數據要發送的時候才能斷開TCP。而客戶端發出FIN報文時只能保證客戶端沒有數據發了,服務端還有沒有數據發客戶端是不知道的。而服務端收到客戶端的FIN報文後只能先回復客戶端一個確認報文來告訴客戶端我服務端已經收到你的FIN報文了,但我服務端還有一些數據沒發完,等這些數據發完了服務端才能給客戶端發FIN報文(所以不能一次性將確認報文和FIN報文發給客戶端,就是這裡多出來了一次)。

4.3 為什麼客戶端發出第四次揮手的確認報文後要等2MSL的時間才能釋放TCP連接?

這裡同樣是要考慮丟包的問題,如果第四次揮手的報文丟失,服務端沒收到確認ack報文就會重發第三次揮手的報文,這樣報文一去一回最長時間就是2MSL,所以需要等這麼長時間來確認服務端確實已經收到了。

4.4 如果已經建立了連接,但是客戶端突然出現故障了怎麼辦?

TCP設有一個保活計時器,客戶端如果出現故障,服務器不能一直等下去,白白浪費資源。服務器每收到一次客戶端的請求後都會重新復位這個計時器,時間通常是設置為2小時,若兩小時還沒有收到客戶端的任何數據,服務器就會發送一個探測報文段,以後每隔75秒鐘發送一次。若一連發送10個探測報文仍然沒反應,服務器就認為客戶端出了故障,接著就關閉連接。


分享到:


相關文章: