面試必備!TCP協議經典十五連問!

前言

TCP協議是大廠面試必問的知識點。整理了15道非常經典的TCP面試題,希望大家都找到理想的offer呀


1.  講下TCP三次握手流程

開始客戶端和服務器都處於CLOSED狀態,然後服務端開始監聽某個端口,進入LISTEN狀態

  • 第一次握手(SYN=1, seq=x),發送完畢後,客戶端進入 SYN_SEND 狀態
  • 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1), 發送完畢後,服務器端進入 SYN_RCVD 狀態。
  • 第三次握手(ACK=1,ACKnum=y+1),發送完畢後,客戶端進入 ESTABLISHED 狀態,當服務器端接收到這個包時,也進入 ESTABLISHED 狀態,TCP 握手,即可以開始數據傳輸。

2.TCP握手爲什麼是三次,不能是兩次?不能是四次?

TCP握手爲什麼是三次呢?爲了方便理解,我們以談戀愛爲例子:兩個人能走到一起,最重要的事情就是相愛,就是我愛你,並且我知道,你也愛我,接下來我們以此來模擬三次握手的過程:

爲什麼握手不能是兩次呢?

如果只有兩次握手,女孩子可能就不知道,她的那句我也愛你,男孩子是否收到,戀愛關係就不能愉快展開。

爲什麼握手不能是四次呢?

因爲握手不能是四次呢?因爲三次已經夠了,三次已經能讓雙方都知道:你愛我,我也愛你。而四次就多餘了。

3. 講下TCP四次揮手過程

  1. 第一次揮手(FIN=1,seq=u),發送完畢後,客戶端進入FIN_WAIT_1 狀態
  2. 第二次揮手(ACK=1,ack=u+1,seq =v),發送完畢後,服務器端進入CLOSE_WAIT 狀態,客戶端接收到這個確認包之後,進入 FIN_WAIT_2 狀態
  3. 第三次揮手(FIN=1,ACK1,seq=w,ack=u+1),發送完畢後,服務器端進入LAST_ACK 狀態,等待來自客戶端的最後一個ACK。
  4. 第四次揮手(ACK=1,seq=u+1,ack=w+1),客戶端接收到來自服務器端的關閉請求,發送一個確認包,並進入 TIME_WAIT狀態, 等待了某個固定時間(兩個最大段生命週期,2MSL,2 Maximum Segment Lifetime)之後,沒有收到服務器端的 ACK ,認爲服務器端已經正常關閉連接,於是自己也關閉連接,進入 CLOSED 狀態。服務器端接收到這個確認包之後,關閉連接,進入 CLOSED 狀態。

4. TCP揮手爲什麼需要四次呢?

舉個例子吧!

小明和小紅打電話聊天,通話差不多要結束時,小紅說“我沒啥要說的了”,小明回答“我知道了”。但是小明可能還會有要說的話,小紅不能要求小明跟着自己的節奏結束通話,於是小明可能又嘰嘰歪歪說了一通,最後小明說“我說完了”,小紅回答“知道了”,這樣通話纔算結束。

5. TIME-WAIT 狀態爲什麼需要等待 2MSL

2MSL,2 Maximum Segment Lifetime,即兩個最大段生命週期

  • 1個 MSL 保證四次揮手中主動關閉方最後的 ACK 報文能最終到達對端
  • 1個 MSL 保證對端沒有收到 ACK 那麼進行重傳的 FIN 報文能夠到達

6.TCP 和 UDP 的區別

  1. TCP面向連接((如打電話要先撥號建立連接);UDP是無連接的,即發送數據之前不需要建立連接。
  2. TCP要求安全性,提供可靠的服務,通過TCP連接傳送的數據,不丟失、不重複、安全可靠。而UDP盡最大努力交付,即不保證可靠交付。
  3. TCP是點對點連接的,UDP一對一,一對多,多對多都可以
  4. TCP傳輸效率相對較低,而UDP傳輸效率高,它適用於對高速傳輸和實時性有較高的通信或廣播通信。
  5. TCP適合用於網頁,郵件等;UDP適合用於視頻,語音廣播等
  6. TCP面向字節流,UDP面向報文

7. TCP報文首部有哪些字段,說說其作用

  • 16位端口號:源端口號,主機該報文段是來自哪裏;目標端口號,要傳給哪個上層協議或應用程序
  • 32位序號:一次TCP通信(從TCP連接建立到斷開)過程中某一個傳輸方向上的字節流的每個字節的編號。
  • 32位確認號:用作對另一方發送的tcp報文段的響應。其值是收到的TCP報文段的序號值加1。
  • 4位頭部長度:表示tcp頭部有多少個32bit字(4字節)。因爲4位最大能標識15,所以TCP頭部最長是60字節。
  • 6位標誌位:URG(緊急指針是否有效),ACk(表示確認號是否有效),PSH(緩衝區尚未填滿),RST(表示要求對方重新建立連接),SYN(建立連接消息標誌接),FIN(表示告知對方本端要關閉連接了)
  • 16位窗口大小:是TCP流量控制的一個手段。這裏說的窗口,指的是接收通告窗口。它告訴對方本端的TCP接收緩衝區還能容納多少字節的數據,這樣對方就可以控制發送數據的速度。
  • 16位校驗和:由發送端填充,接收端對TCP報文段執行CRC算法以檢驗TCP報文段在傳輸過程中是否損壞。注意,這個校驗不僅包括TCP頭部,也包括數據部分。這也是TCP可靠傳輸的一個重要保障。
  • 16位緊急指針:一個正的偏移量。它和序號字段的值相加表示最後一個緊急數據的下一字節的序號。因此,確切地說,這個字段是緊急指針相對當前序號的偏移,不妨稱之爲緊急偏移。TCP的緊急指針是發送端向接收端發送緊急數據的方法。

8. TCP 是如何保證可靠性的

  • 首先,TCP的連接是基於 三次握手,而斷開則是 四次揮手。確保連接和斷開的可靠性。
  • 其次,TCP的可靠性,還體現在 有狀態;TCP會記錄哪些數據發送了,哪些數據被接受了,哪些沒有被接受,並且保證數據包按序到達,保證數據傳輸不出差錯。
  • 再次,TCP的可靠性,還體現在 可控制。它有報文校驗、ACK應答、 超時重傳(發送方)、失序數據重傳(接收方)、丟棄重複數據、流量控制(滑動窗口)和擁塞控制等機制。

9. TCP 重傳機制

超時重傳

TCP 爲了實現可靠傳輸,實現了重傳機制。最基本的重傳機制,就是超時重傳,即在發送數據報文時,設定一個定時器,每間隔一段時間,沒有收到對方的ACK確認應答報文,就會重發該報文。

這個間隔時間,一般設置爲多少呢?我們先來看下什麼叫RTT(Round-Trip Time,往返時間)

RTT就是,一個數據包從發出去到回來的時間,即數據包的一次往返時間。超時重傳時間,就是Retransmission Timeout ,簡稱RTO

RTO設置多久呢?

  • 如果RTO比較小,那很可能數據都沒有丟失,就重發了,這會導致網絡阻塞,會導致更多的超時出現。
  • 如果RTO比較大,等到花兒都謝了還是沒有重發,那效果就不好了。

一般情況下,RTO略大於RTT,效果是最好的。一些小夥伴會問,超時時間有沒有計算公式呢?有的!有個標準方法算RTO的公式,也叫Jacobson / Karels 算法。我們一起來看下計算RTO的公式

1. 先計算SRTT(計算平滑的RTT)

SRTT = (1 - α) * SRTT + α * RTT  //求 SRTT 的加權平均

2. 再計算RTTVAR (round-trip time variation)

RTTVAR = (1 - β) * RTTVAR + β * (|RTT - SRTT|) //計算 SRTT 與真實值的差距

3. 最終的RTO

RTO = µ * SRTT + ∂ * RTTVAR  =  SRTT + 4·RTTVAR  

其中,α = 0.125,β = 0.25, μ = 1,∂ = 4,這些參數都是大量結果得出的最優參數。

但是,超時重傳會有這些缺點:

  • 當一個報文段丟失時,會等待一定的超時週期然後才重傳分組,增加了端到端的時延。
  • 當一個報文段丟失時,在其等待超時的過程中,可能會出現這種情況:其後的報文段已經被接收端接收但卻遲遲得不到確認,發送端會認爲也丟失了,從而引起不必要的重傳,既浪費資源也浪費時間。

並且,TCP有個策略,就是超時時間間隔會加倍。超時重傳需要等待很長時間。因此,還可以使用快速重傳機制。

快速重傳

快速重傳機制,它不以時間驅動,而是以數據驅動。它基於接收端的反饋信息來引發重傳。

一起來看下快速重傳流程:

快速重傳流程

發送端發送了 1,2,3,4,5,6 份數據:

  • 第一份 Seq=1 先送到了,於是就 Ack 回 2;
  • 第二份 Seq=2 也送到了,假設也正常,於是ACK 回 3;
  • 第三份 Seq=3 由於網絡等其他原因,沒送到;
  • 第四份 Seq=4 也送到了,但是因爲Seq3沒收到。所以ACK回3;
  • 後面的 Seq=4,5的也送到了,但是ACK還是回覆3,因爲Seq=3沒收到。
  • 發送端連着收到三個重複冗餘ACK=3的確認(實際上是4個,但是前面一個是正常的ACK,後面三個纔是重複冗餘的),便知道哪個報文段在傳輸過程中丟失了,於是在定時器過期之前,重傳該報文段。
  • 最後,接收到收到了 Seq3,此時因爲 Seq=4,5,6都收到了,於是ACK回7.

快速重傳還可能會有個問題:ACK只向發送端告知最大的有序報文段,到底是哪個報文丟失了呢?並不確定!那到底該重傳多少個包呢?

是重傳 Seq3 呢?還是重傳 Seq3、Seq4、Seq5、Seq6 呢?因爲發送端並不清楚這三個連續的 ACK3 是誰傳回來的。

帶選擇確認的重傳(SACK)

爲了解決快速重傳的問題:應該重傳多少個包? TCP提供了SACK方法(帶選擇確認的重傳,Selective Acknowledgment)。

SACK機制就是,在快速重傳的基礎上,接收端返回最近收到的報文段的序列號範圍,這樣發送端就知道接收端哪些數據包沒收到,醬紫就很清楚該重傳哪些數據包啦。SACK標記是加在TCP頭部選項字段裏面的。

SACK機制

如上圖中,發送端收到了三次同樣的ACK=30的確認報文,於是就會觸發快速重發機制,通過SACK信息發現只有30~39這段數據丟失,於是重發時就只選擇了這個30~39的TCP報文段進行重發。

D-SACK

D-SACK,即Duplicate SACK(重複SACK),在SACK的基礎上做了一些擴展,,主要用來告訴發送方,有哪些數據包自己重複接受了。DSACK的目的是幫助發送方判斷,是否發生了包失序、ACK丟失、包重複或僞重傳。讓TCP可以更好的做網絡流控。來看個圖吧:

D-SACK簡要流程

10. 聊聊TCP的滑動窗口

TCP 發送一個數據,需要收到確認應答,纔會發送下一個數據。這樣有個缺點,就是效率會比較低。

這就好像我們面對面聊天,你說完一句,我應答後,你纔會說下一句。那麼,如果我在忙其他事情,沒有能夠及時回覆你。你說完一句後,要等到我忙完回覆你,你才說下句,這顯然很不現實。

爲了解決這個問題,TCP引入了窗口,它是操作系統開闢的一個緩存空間。窗口大小值表示無需等待確認應答,而可以繼續發送數據的最大值。

TCP頭部有個字段叫win,也即那個16位的窗口大小,它告訴對方本端的TCP接收緩衝區還能容納多少字節的數據,這樣對方就可以控制發送數據的速度,從而達到流量控制的目的。

通俗點講,就是接受方每次收到數據包,在發送確認報文的時候,同時告訴發送方,自己的緩存區還有多少空餘空間,緩衝區的空餘空間,我們就稱之爲接受窗口大小。這就是win。

TCP 滑動窗口分爲兩種: 發送窗口和接收窗口。發送端的滑動窗口包含四大部分,如下:

  • 已發送且已收到ACK確認
  • 已發送但未收到ACK確認
  • 未發送但可以發送
  • 未發送也不可以發送
  • 虛線矩形框,就是發送窗口。
  • SND.WND: 表示發送窗口的大小,上圖虛線框的格子數就是14個。
  • SND.UNA: 一個絕對指針,它指向的是已發送但未確認的第一個字節的序列號。
  • SND.NXT:下一個發送的位置,它指向未發送但可以發送的第一個字節的序列號。

接收方的滑動窗口包含三大部分,如下:

  • 已成功接收並確認
  • 未收到數據但可以接收
  • 未收到數據並不可以接收的數據
  • 虛線矩形框,就是接收窗口。
  • REV.WND: 表示接收窗口的大小,上圖虛線框的格子就是9個。
  • REV.NXT:下一個接收的位置,它指向未收到但可以接收的第一個字節的序列號。

11. 聊聊TCP的流量控制

TCP三次握手,發送端和接收端進入到ESTABLISHED狀態,它們即可以愉快地傳輸數據啦。

但是發送端不能瘋狂地向接收端發送數據,因爲接收端接收不過來的話,接收方只能把處理不過來的數據存在緩存區裏。如果緩存區都滿了,發送方還在瘋狂發送數據的話,接收方只能把收到的數據包丟掉,這就浪費了網絡資源啦。

TCP 提供一種機制可以讓發送端根據接收端的實際接收能力控制發送的數據量,這就是流量控制

TCP通過滑動窗口來控制流量,我們看下流量控制的簡要流程吧:

首先雙方三次握手,初始化各自的窗口大小,均爲 400 個字節。

TCP的流量控制
  1. 假如當前發送方給接收方發送了200個字節,那麼,發送方的 SND.NXT會右移200個字節,也就是說當前的可用窗口減少了200 個字節。
  2. 接受方收到後,放到緩衝隊列裏面,REV.WND =400-200=200字節,所以win=200字節返回給發送方。接收方會在 ACK 的報文首部帶上縮小後的滑動窗口200字節
  3. 發送方又發送200字節過來,200字節到達,繼續放到緩衝隊列。不過這時候,由於大量負載的原因,接受方處理不了這麼多字節,只能處理100字節,剩餘的100字節繼續放到緩衝隊列。這時候,REV.WND = 400-200-100=100字節,即win=100返回發送方。
  4. 發送方繼續幹活,發送100字節過來,這時候,接受窗口win變爲0。
  5. 發送方停止發送,開啓一個定時任務,每隔一段時間,就去詢問接受方,直到win大於0,才繼續開始發送。

12. TCP的擁塞控制

擁塞控制是作用於網絡的,防止過多的數據包注入到網絡中,避免出現網絡負載過大的情況。它的目標主要是最大化利用網絡上瓶頸鍊路的帶寬。它跟流量控制又有什麼區別呢?流量控制是作用於接收者的,根據接收端的實際接收能力控制發送速度,防止分組丟失的。

我們可以把網絡鏈路比喻成一根水管,如果我們想最大化利用網絡來傳輸數據,那就是儘快讓水管達到最佳充滿狀態。

發送方維護一個擁塞窗口cwnd(congestion window) 的變量,用來估算在一段時間內這條鏈路(水管)可以承載和運輸的數據(水)的數量。它大小代表着網絡的擁塞程度,並且是動態變化的,但是爲了達到最大的傳輸效率,我們該如何知道這條水管的運送效率是多少呢?

一個比較簡單的方法就是不斷增加傳輸的水量,直到水管快要爆裂爲止(對應到網絡上就是發生丟包),用 TCP 的描述就是:

只要網絡中沒有出現擁塞,擁塞窗口的值就可以再增大一些,以便把更多的數據包發送出去,但只要網絡出現擁塞,擁塞窗口的值就應該減小一些,以減少注入到網絡中的數據包數。

實際上,擁塞控制主要有這幾種常用算法

  • 慢啓動
  • 擁塞避免
  • 擁塞發生
  • 快速恢復

慢啓動算法

慢啓動算法,表面意思就是,別急慢慢來。它表示TCP建立連接完成後,一開始不要發送大量的數據,而是先探測一下網絡的擁塞程度。由小到大逐漸增加擁塞窗口的大小,如果沒有出現丟包,每收到一個ACK,就將擁塞窗口cwnd大小就加1(單位是MSS)每輪次發送窗口增加一倍,呈指數增長,如果出現丟包,擁塞窗口就減半,進入擁塞避免階段。

  • TCP連接完成,初始化cwnd = 1,表明可以傳一個MSS單位大小的數據。
  • 每當收到一個ACK,cwnd就加一;
  • 每當過了一個RTT,cwnd就增加一倍; 呈指數讓升

爲了防止cwnd增長過大引起網絡擁塞,還需設置一個慢啓動閥值ssthresh(slow start threshold)狀態變量。當cwnd到達該閥值後,就好像水管被關小了水龍頭一樣,減少擁塞狀態。即當cwnd >ssthresh時,進入了擁塞避免算法。

擁塞避免算法

一般來說,慢啓動閥值ssthresh是65535字節,cwnd到達慢啓動閥值

  • 每收到一個ACK時,cwnd = cwnd + 1/cwnd
  • 當每過一個RTT時,cwnd = cwnd + 1

顯然這是一個線性上升的算法,避免過快導致網絡擁塞問題。

擁塞發生

當網絡擁塞發生丟包時,會有兩種情況:

  • RTO超時重傳
  • 快速重傳

如果是發生了RTO超時重傳,就會使用擁塞發生算法

  • 慢啓動閥值sshthresh =  cwnd /2
  • cwnd 重置爲 1
  • 進入新的慢啓動過程

這真的是辛辛苦苦幾十年,一朝回到解放前。其實還有更好的處理方式,就是快速重傳。發送方收到3個連續重複的ACK時,就會快速地重傳,不必等待RTO超時再重傳。

image.png

慢啓動閥值ssthresh 和 cwnd 變化如下:

  • 擁塞窗口大小 cwnd = cwnd/2
  • 慢啓動閥值 ssthresh = cwnd
  • 進入快速恢復算法

快速恢復

快速重傳和快速恢復算法一般同時使用。快速恢復算法認爲,還有3個重複ACK收到,說明網絡也沒那麼糟糕,所以沒有必要像RTO超時那麼強烈。

正如前面所說,進入快速恢復之前,cwnd 和 sshthresh已被更新:

- cwnd = cwnd /2
- sshthresh = cwnd

然後,真正的快速算法如下:

  • cwnd = sshthresh  + 3
  • 重傳重複的那幾個ACK(即丟失的那幾個數據包)
  • 如果再收到重複的 ACK,那麼 cwnd = cwnd +1
  • 如果收到新數據的 ACK 後, cwnd = sshthresh。因爲收到新數據的 ACK,表明恢復過程已經結束,可以再次進入了擁塞避免的算法了。

13. 半連接隊列和 SYN Flood 攻擊的關係

TCP進入三次握手前,服務端會從CLOSED狀態變爲LISTEN狀態,同時在內部創建了兩個隊列:半連接隊列(SYN隊列)和全連接隊列(ACCEPT隊列)。

什麼是半連接隊列(SYN隊列) 呢? 什麼是全連接隊列(ACCEPT隊列) 呢?回憶下TCP三次握手的圖:

三次握手
  • TCP三次握手時,客戶端發送SYN到服務端,服務端收到之後,便回覆 ACK和SYN,狀態由 LISTEN變爲SYN_RCVD,此時這個連接就被推入了 SYN隊列,即半連接隊列。
  • 當客戶端回覆ACK, 服務端接收後,三次握手就完成了。這時連接會等待被具體的應用取走,在被取走之前,它被推入ACCEPT隊列,即全連接隊列。

SYN Flood是一種典型的DoS (Denial of Service,拒絕服務) 攻擊,它在短時間內,僞造不存在的IP地址,向服務器大量發起SYN報文。當服務器回覆SYN+ACK報文後,不會收到ACK迴應報文,導致服務器上建立大量的半連接半連接隊列滿了,這就無法處理正常的TCP請求啦。

主要有 syn cookieSYN Proxy防火牆等方案應對。

  • syn cookie:在收到SYN包後,服務器根據一定的方法,以數據包的源地址、端口等信息爲參數計算出一個cookie值作爲自己的SYNACK包的序列號,回覆SYN+ACK後,服務器並不立即分配資源進行處理,等收到發送方的ACK包後,重新根據數據包的源地址、端口計算該包中的確認序列號是否正確,如果正確則建立連接,否則丟棄該包。

  • SYN Proxy防火牆:服務器防火牆會對收到的每一個SYN報文進行代理和迴應,並保持半連接。等發送方將ACK包返回後,再重新構造SYN包發到服務器,建立真正的TCP連接。

14. Nagle 算法與延遲確認

Nagle算法

如果發送端瘋狂地向接收端發送很小的包,比如就1個字節,那麼親愛的小夥伴,你們覺得會有什麼問題呢?

TCP/IP協議中,無論發送多少數據,總是要在數據前面加上協議頭,同時,對方接收到數據,也需要發送ACK表示確認。爲了儘可能的利用網絡帶寬,TCP總是希望儘可能的發送足夠大的數據。Nagle算法就是爲了儘可能發送大塊數據,避免網絡中充斥着許多小數據塊。

Nagle算法的基本定義是:任意時刻,最多隻能有一個未被確認的小段。所謂“小段”,指的是小於MSS尺寸的數據塊,所謂“未被確認”,是指一個數據塊發送出去後,沒有收到對方發送的ACK確認該數據已收到。

Nagle算法的實現規則:

  • 如果包長度達到MSS,則允許發送;
  • 如果該包含有FIN,則允許發送;
  • 設置了TCP_NODELAY選項,則允許發送;
  • 未設置TCP_CORK選項時,若所有發出去的小數據包(包長度小於MSS)均被確認,則允許發送;
  • 上述條件都未滿足,但發生了超時(一般爲200ms),則立即發送。

延遲確認

如果接受方剛接收到發送方的數據包,在很短很短的時間內,又接收到第二個包。那麼請問接收方是一個一個地回覆好點,還是合併一起回覆好呢?

接收方收到數據包後,如果暫時沒有數據要發給對端,它可以等一段時再確認(Linux上默認是40ms)。如果這段時間剛好有數據要傳給對端,ACK就隨着數據傳輸,而不需要單獨發送一次ACK。如果超過時間還沒有數據要發送,也發送ACK,避免對端以爲丟包。

但是有些場景不能延遲確認,比如發現了亂序包接收到了大於一個 frame 的報文,且需要調整窗口大小等。

一般情況下,Nagle算法和延遲確認不能一起使用,Nagle算法意味着延遲發,延遲確認意味着延遲接收,醬紫就會造成更大的延遲,會產生性能問題。

15. TCP的粘包和拆包

TCP是面向流,沒有界限的一串數據。TCP底層並不瞭解上層業務數據的具體含義,它會根據TCP緩衝區的實際情況進行包的劃分,所以在業務上認爲,一個完整的包可能會被TCP拆分成多個包進行發送也有可能把多個小的包封裝成一個大的數據包發送,這就是所謂的TCP粘包和拆包問題。

TCP的粘包和拆包

爲什麼會產生粘包和拆包呢?

  • 要發送的數據小於TCP發送緩衝區的大小,TCP將多次寫入緩衝區的數據一次發送出去,將會發生粘包;
  • 接收數據端的應用層沒有及時讀取接收緩衝區中的數據,將發生粘包;
  • 要發送的數據大於TCP發送緩衝區剩餘空間大小,將會發生拆包;
  • 待發送數據大於MSS(最大報文長度),TCP在傳輸前將進行拆包。即TCP報文長度-TCP頭部長度>MSS。

解決方案:

  • 發送端將每個數據包封裝爲固定長度
  • 在數據尾部增加特殊字符進行分割
  • 將數據分爲兩部分,一部分是頭部,一部分是內容體;其中頭部結構大小固定,且有一個字段聲明內容體的大小。

參考資料

[1]

TCP 的那些事兒(下): https://coolshell.cn/articles/11609.html

[2]

面試頭條你需要懂的 TCP 擁塞控制原理: https://zhuanlan.zhihu.com/p/76023663

[3]

30張圖解:TCP 重傳、滑動窗口、流量控制、擁塞控制發愁: https://zhuanlan.zhihu.com/p/133307545

[4]

TCP協議靈魂之問,鞏固你的網路底層基礎: https://juejin.cn/post/6844904070889603085

[5]

TCP粘包和拆包: https://blog.csdn.net/ailunlee/article/details/95944377


本文分享自微信公衆號 - JAVA日知錄(javadaily)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章