字節面試被虐後,是時候搞懂 TCP 數據可靠傳輸了

本人這段時間參加了一些面試,先總結一下被問到的關於 TCP 的問題前端

  1. 描述一下 TCP 的三次握手,第一次握手的報文段裏除了 SYN 標誌位,還包含了什麼?面試

  2. 描述一下 TCP 的四次揮手,爲何不能是三次揮手?第四次揮手客戶端發出最後的確認報文段以後,就會立刻斷開鏈接嗎?緩存

  3. TCP 是一個包發送出去並獲得確認後,才能夠發送下一個包,仍是無需等待確認就能夠發送下一個包?markdown

  4. TCP 和 UDP 的區別是什麼?網絡

  5. TCP 是怎麼保證可靠數據傳輸的?oop

  6. TCP 是如何進行流量控制的?性能

  7. TCP 是如何進行擁塞控制的?spa

  8. TCP 的慢啓動是什麼?快啓動又是什麼?計算機網絡

    以上每個問題,都是我在不一樣的面試中被問到過的,其中 二、三、四、五、8 都是在同一輪字節面試中被問到的,可見把網絡基礎打實仍是很重要的。對於數據的可靠傳輸這塊,我以前的理解不太完整,知道 TCP 有丟包重傳的機制,可是要我比較清晰地描述出來,仍是比較困難。code

因此打算經過這篇文章來捋一捋。若有不妥之處,但願你們可以幫忙指出,共同進步。

什麼是可靠數據傳輸

TCP 的可靠數據傳輸服務確保一臺主機上的一個進程,發送給另外一個主機上的一個進程的數據,是無損和按序的。

序號、確認號

TCP 報文段由首部字段和一個數據字段組成,數據字段包含了應用數據,首部則包含了跟三次握手、流量控制、擁塞控制、可靠傳輸等相關的一系列字段,下圖展現了 TCP 報文段的首部結構:

TCP 報文段結構.png

本文咱們只關心跟可靠數據傳輸相關的首部字段,也就是序列號和確認號,這兩個字段是 TCP 可靠數據傳輸的關鍵部分。上圖的單位爲字節,因此序列號和確認號各佔 4 個字節,即 32 比特。

咱們先假設主機 A 和主機 B 創建了 TCP 鏈接,它們能夠向對方發送數據。

  • 序號

好比主機 A 發送給主機 B 一個序號爲 1000 的報文段,且該報文段中包含 500 字節的數據,那麼,主機 A 下一個發往主機 B 的報文段的序號就應該是 1500,注意,不是 1001。

序號字段包含了該報文段首字節的字節流編號。 注意,序號不都是從 0 開始,而且通常不會從 0 開始,發送端和接收端在三次握手時,會隨機生成一個初始序號,並告訴對方,接下來我傳輸的數據會從這個序號開始。

  • 確認號

好比主機 A 發送給主機 B 一個序號爲 1000 的報文段,且該報文段中包含 500 字節的數據,那麼主機 B 在發送給主機 A 的 ACK 報文段中,就會將確認號置爲 1500,表示 1500 以前的數據我已經都拿到了,接下來你給我發 1500 以後的數據吧。而主機 A 在收到這個 ACK 報文後,就會知道主機 B 已經收到了 1500 以前的那個報文段。

主機 B 填充進報文段的確認號,是它指望從主機 A 收到的下一字節的序號,同時主機 B 也是在經過這個確認號告訴主機 A,以前的報文段我已經收到了。

TCP 鏈接是一條「流水線」

咱們思考一下文章開頭的第 3 個問題:TCP 是一個包發送出去並獲得確認後,才能夠發送下一個包,仍是無需等待確認就能夠發送下一個包?

讓咱們想象一下,若是是一個包發送出去並獲得確認後,才能夠發送下一個包,也就是須要等待至少一個 RTT(往返時延),才能發送下一個包,若是算上在發送方和接收方的底層協議處理時間、中間任何一臺路由器的處理與排隊時延,這個等待時間會更長。這樣的性能是沒法接受的。

若是某個協議採起了上面描述的這種行爲,咱們稱之爲停等協議,顯然,TCP 不是停等協議,TCP 容許發送方發送多個報文段而無須等待確認,這些報文段就像被填充到一條流水線裏同樣,因此這種技術被稱爲流水線

發送端的限制

那在流水線上能夠同時存在的報文段(已發送但未被確認的報文段)的數量是沒有限制的嗎?

顯然不是。

有限制,而且這個限制會根據接收端的接收能力和網絡的擁塞狀況作調整。在接收端的接收能力比較強或者網絡比較不擁堵的時候,容許發送更多的報文段;相反,在接收端的接收能力比較弱或者網絡出現擁堵的時候,容許發送較少的報文段。這就涉及到 TCP 的流量控制擁塞控制,本文不作展開。

如今咱們只須要知道,每一個 TCP 發送方會維護兩個變量,一個是 SendBase,表示最小的未確認報文段的序號,另外一個是 NextSeqNum,表示最小的未使用序號(即下一個待發報文段的序號),下圖將序號範圍分割成了 4 段:

TCP 發送窗口.png

「發送,還未確認」 以及 「可用,還未發送」 的序號,就是當前可用的序號,大於或等於 SendBase + N 的序號是不能使用的。一旦「發送,還未確認」的報文段數量達到了 N,這個 TCP 發送方就沒法再發送下一個報文段。N 一般被稱爲窗口長度。咱們假設窗口長度爲 4,每一個報文段的數據大小都爲 1 個字節,且發送方始終有包可發,下圖展現了無丟包時的數據傳輸過程:

TCP(不丟包).png

(圖中接收方維護一個接收窗口,跟發送方的窗口徹底不是一個東西,你們不要混淆,在本文的討論中能夠忽略接收窗口的存在)

由圖可見,發送了報文段 3 以後,窗口滿,發送方就沒法繼續發送報文段 4。直到 ACK 0 到達發送端,TCP 會更新 SendBase 的值,前面咱們提到過,SendBase 指的是最小的未確認報文段的序號,此時這個序號應爲 1,因此發送端會將 SendBase 置爲 1,將窗口右移,變爲 1 2 3 4,這時候,序號爲 4 的報文段纔可以被髮送。

想象一下,若是因爲某些網絡緣由,上圖中的 ACK 0、ACK 1 丟失,當 ACK 2 到達發送端的時候,窗口應該怎麼變化?

bqb9.png

公佈答案,發送端收到 ACK 2 以後,就會直接將窗口右移,窗口變爲 3 4 5 6,是否是有點小意外?怎麼無論 ACK 0 和 ACK 1 了?

之因此會這樣,是由於 TCP 採用的是累積確認,ACK 2 其實是在告訴發送端,2 及 2 以前的全部字節我都已經收到了,這個 ACK 是在確認一個或多個先前未被確認的報文段。所以發送端會直接將 SendBase 更新爲最小的已發送待確認的序號,也就是 3。

瞭解了窗口的概念,以及 TCP 是如何經過窗口對發送端進行限制的以後,咱們就能夠進一步來看看 TCP 是怎麼保證數據的可靠傳輸的。

可靠數據傳輸的原理

數據可靠傳輸的本質,簡單來講,就是我把一個報文段發出去以後,我要知道對方有沒有成功收到這個報文段,當我發現對方沒收到時,就得從新發送一次這個報文段,直到對方確認收到爲止。

TCP 是怎麼感知到丟包的?

TCP 遇到如下兩種狀況時,就會認爲丟包了

定時器超時

TCP 發送端會維護一個單必定時器,咱們能夠簡單地理解爲,定時器是跟窗口綁定的,定時器始終監控着窗口中第一個包的狀態,窗口發生變化,就必須重啓定時器。因而,定時器超時意味着,當前窗口最前面的包丟失了,也就是 SendBase 對應的包丟失了,須要重傳這個包。

下圖展現了定時器超時的狀況下,TCP 是怎麼處理的:

TCP(超時).png

結合上圖,咱們能夠發現:

  • 當發送端接收到 ACK 0 和 ACK 1 後,窗口向右移動,有效窗口爲 2 3 4 5,此時的 SendBase 是 2,以後窗口就沒有再右移

bqb10.png

「你剛剛不是說,TCP 採用的是累積確認嗎?報文段 2 丟失了,沒有 ACK 2,那後面要是收到了 ACK 三、ACK 4,窗口不就右移了?」

emmm...

甚是有理,但 TCP 不會幹這麼自相矛盾的事情,咱們再仔細看看上面的圖,接收方在收到報文段 三、四、5 的時候,並無回覆對應的 ACK 三、四、5,而是都回復了 ACK 1(緣由咱們下一節會細講),因此發送端根本收不到 2 及 2 以後的任何 ACK。

再等等,這,是否是恰好驗證了 TCP 的累積確認?若是接收端在還沒收到報文段 2 的時候,收到了報文段 3,就回復了 ACK 3,那這個 ACK 3,並不能達到累積確認的效果,由於報文段 2 實際上還不能被確認。有點繞,你細品。

好像扯得有點遠了...咱們回到定時器

  • 從圖中能夠看到,定時器超時的時候,當前的窗口是 2 3 4 5SendBase 是 2,說明報文段 2 丟失了,發送端因而重傳報文段 2

  • 你們注意,接收端收到序號爲 三、四、5 的失序報文時,並不會將其丟棄,而是將其緩存起來,等到指望報文 2 到達後,再將 二、三、四、5 一併交付給上層應用

收到 3 個冗餘 ACK

在上面的例子中,咱們提到了,當接收端收到序號爲 三、四、5 的報文段時,因爲尚未收到序號爲 2 的報文段,接收端不會回覆 ACK 三、四、5,而是統一回復了 ACK 1,這幾個 ACK 1 就是冗餘 ACK

當 TCP 接收端收到比指望序號大的失序報文時,會當即發送冗餘 ACK,對最後一個按序的報文段進行確認,將冗餘 ACK 的確認號置爲期待的下一個按序的序號,告訴發送端,這個序號的報文我還沒收到呢,怎麼就給我發了更大序號的報文?

仍是看一個例子:

TCP(冗餘 ACK).png

如圖所示,接收端收到序號爲 三、四、5 的失序報文段時,會返回 ACK 1,ACK 1 是對序號爲 1 的報文段的確認報文,該確認報文會將確認號置爲 2,表示我指望的下一個報文段的序號是 2,怎麼 2 還沒收到,就給我發了 三、四、5,趕忙給我發 2 的報文段。

而發送端在定時器過時以前,連續收到了 3 個 ACK 1,發送端就會意識到,這個被冗餘確認了 3 次的報文段後面的那個報文段(報文段 2)已經丟失了。這時候,TCP 發送端就會進行快速重傳,即不用等到定時器過時,當即重傳丟失的報文段。

TCP 意識到丟包後,作了什麼操做?

其實上面的內容裏已經提到了,TCP 意識到丟包後,就會重傳丟失的那個報文段。

對於經過定時器超時意識到的丟包,TCP 會重傳具備最小序號但仍未被確認的報文段;經過冗餘 ACK 意識到的丟包,TCP 會重傳冗餘 ACK 中的確認號對應的報文段。

小結

TCP 經過兩種方式判斷丟包,定時器超時,或收到 3 個相同確認號的冗餘 ACK。前者經過觀察當前窗口,判斷丟的是哪一個包,後者經過冗餘 ACK 裏的確認號,判斷丟的是哪一個包。意識到丟包後,TCP 會重傳該包。

總結

本文舉的都是很簡單的例子,實際的場景要比這複雜得多,涉及到的知識點也不少。TCP 是前端面試的常見問題,想要進大廠的朋友,只簡單地瞭解三次握手、四次揮手已經遠遠不夠了(固然若是你有其餘突出的亮點就當我沒說)。共勉。

參考

計算機網絡:自頂向下方法

相關文章
相關標籤/搜索