面試準備 TCP 知識,看這一篇就夠了

前言

TCP(Transmission Control Protocol,傳輸控制協議) 是計算機網絡的的重要組成部分,也是網絡編程的重要內容,還有咱們平時接觸最多的 HTTP 也是基於 TCP 實現的。TCP 能夠說是最重要的傳輸層協議,既然如此,做爲開發人員,就有必要把 TCP 的核心概念和原理搞清楚。除此以外,諸如三次握手、四次揮手、滑動窗口和擁塞控制這些概念更是高頻面試題,這就更有理由深刻學習一下 TCP 了,本文就爲你們詳細梳理一下 TCP 的核心概念和原理。注:因爲本文圖片較多,標註有 by HYN 的圖片爲做者自制,其它來自於網絡或參考資料,侵刪面試

1、TCP 簡介

第一部分先爲你們介紹一下 TCP 的主要概念,並講解一下 TCP 的三個重要特性——1. 面向鏈接;2. 基於字節流;3. 可靠性。算法

關於網絡分層的概念實在是老生常談了,下圖就是兩種經典的分層模型,能夠看到 TCP 在網絡分層中的位置。編程

網絡分層模型.png

網絡分層模型

本文重點對 TCP 進行介紹,從圖中能夠看到 TCP 位於傳輸層,並且構建於網絡層的 IP 協議之上,對於 TCP 最多見的介紹就是 「TCP 是一種面向鏈接的、可靠的、基於字節流的傳輸層通訊協議」,那這三個形容詞到底是什麼意思呢?緩存

1.1 面向鏈接

面向鏈接意味着兩個使用 TCP 的應用 (一般是一個客戶端和一個服務器) 在彼此交換數據以前必須先創建一個 TCP 鏈接。這一過程與打電話很類似,先撥號響鈴,等對方應答後再說明是誰。詳細的三次握手、四次揮手過程將在第二部分——鏈接管理部分進行介紹。服務器

1.2 基於字節流

TCP 鏈接雙方的數據交換格式是以字節 (byte,1byte = 8 bit)構成的有序但無結構的字節流。TCP 不在字節流中插入記錄標識符,這被稱爲字節流服務(byte stream service)。若是一方的應用程序先傳 10 字節,又傳 20 字節,再傳 50 字節 ,鏈接的另外一方將沒法瞭解發方每次發送了多少字節。收方能夠分 4 次接收這 80 個字節,每次接收 20 字節。一端將字節流放到 TCP 鏈接上,一樣的字節流將出如今 TCP 鏈接的另外一端。另外,TCP 對字節流的內容不做任何解釋,TCP 沒法知道傳輸的數據字節流是二進制數據,仍是 ASCI I 字符。cookie

若是以爲上面這段話比較抽象的話,能夠拿 TCP 的字節流和 UDP 的報文 (message) 進行比較(UDP:User Datagram Protocol,用戶數據報協議,和 TCP 同爲傳輸層的協議,後面會提供二者的全面對比)。TCP 的字節流相似於自來水,鏈接雙方都有緩衝區,能夠類比成蓄水池,發送方的發送頻率和每次的發送量沒有固定要求,接收方也能夠自由決定本身的接收頻率和每次的接收量,只要把全部的數據接收完畢便可。而 UDP 的數據報則相似於瓶裝水 (好比農夫山泉),發送方發送一瓶,接收方就要相應地接收一瓶。網絡

下圖描述了 TCP 鏈接中數據的傳輸過程以及 TCP 在整個過程當中所扮演的角色。併發

TCP 在網絡數據傳輸中扮演的角色.png

TCP 在網絡數據傳輸中的位置和角色

按照圖中的流程,好比咱們在瀏覽B站,在 TCP 鏈接創建以後,客戶端的應用層協議能夠向 TCP 發送無特殊格式的字節流,TCP 會將這些字節打包成報文段(segment),報文段大小視狀況而定,這些報文段會被網絡層的 IP 封裝成 IP 數據報(IP Datagram),而後通過網絡傳輸給服務器,而接下來服務器的操做至關於客戶端的逆操做,先從 IP 數據報中拆分出 TCP 報文段,再把 TCP 報文段還原成字節流併發送給上層的應用層協議。服務器向客戶端發送數據的流程也是同樣的,發送方和接收方的角色互換便可。ide

報文段簡介

上面屢次提到了報文段的概念,其結構很是重要,後面的鏈接過程和擁塞控制等內容也要用到相關概念,先在這裏介紹一下。函數

TCP 報文段結構.png

TCP 報文段結構

圖的上半部分顯示 TCP 報文段被封裝在 IP 數據報中,圖的下半部分則顯示了 TCP 報文段和 TCP 首部的結構,TCP 首部的固定數據有20字節,加上選項部分最大可達60字節,而有效數據部分則是被打包的應用層數據。下面介紹一下 TCP 首部的結構:

  • 端口號 (Source Port and Destination Port):每一個 TCP 報文段都包含源端和目的端的端口號,用於尋找發送端和接收端應用進程。這兩個值加上 IP 首部中的源端 IP 地址和目的端 IP 地址就能夠肯定一個惟一的 TCP 鏈接。
  • 序號 (Sequence Number):這個字段的主要做用是用於將失序的數據從新排列。TCP 會隱式地對字節流中的每一個字節進行編號,而 TCP 報文段的序號被設置爲其數據部分的第一個字節的編號。序號是 32 bit 的無符號數,取值範圍是0到 232 - 1。
  • 確認序號 (Acknowledgment Number):接收方在接受到數據後,會回覆確認報文,其中包含確認序號,做用就是告訴發送方本身接收到了哪些數據,下一次數據從哪裏開始發,所以,確認序號應當是上次已成功收到數據字節序號加 1。只有 ACK 標誌爲 1 時確認序號字段纔有效。
  • 首部長度 (Header Length):首部中的選項部分的長度是可變的,所以首部的長度也是可變的,因此須要這個字段來明確表示首部的長度,這個字段佔 4 bit,4 位的二進制數最大能夠表示 15,而首部長度是以 4 個字節爲一個單位的,所以首部最大長度是 15 * 4 = 60 字節。
  • 保留字段 (Reserved):佔 6 位,將來可能有具體用途,目前默認值爲0.
  • 控制位 (Control Bits):在三次握手和四次揮手中會常常看到 SYN、ACK 和 FIN 的身影,一共有 6 個標誌位,它們表示的意義以下:

    • URG (Urgent Bit):值爲 1 時,緊急指針生效
    • ACK (Acknowledgment Bit):值爲 1 時,確認序號生效
    • PSH (Push Bit):接收方應儘快將這個報文段交給應用層
    • RST (Reset Bit):發送端遇到問題,想要重建鏈接
    • SYN (Synchronize Bit):同步序號,用於發起一個鏈接
    • FIN (Finish Bit):發送端要求關閉鏈接
  • 窗口大小 (Window): TCP的流量控制由鏈接的每一端經過聲明的窗口大小來提供。窗口大小爲字節數,起始於確認序號字段指明的值,這個值是接收端正指望接收的字節。窗口大小是一個 16 bit 字段,單位是字節, 於是窗口大小最大爲 65535 字節。
  • 檢驗和 (Checksum):功能相似於數字簽名,用於驗證數據完整性,也就是確保數據未被修改。檢驗和覆蓋了整個 TCP 報文段,包括 TCP 首部和 TCP 數據,發送端根據特定算法對整個報文段計算出一個檢驗和,接收端會進行計算並驗證。
  • 緊急指針 (Urgent Pointer):當 URG 控制位值爲 1 時,此字段生效,緊急指針是一個正的偏移量,和序號字段中的值相加表示緊急數據最後一個字節的序號。 TCP 的緊急方式是發送端向另外一端發送緊急數據的一種方式。
  • 選項 (Options):這一部分是可選字段,也就是非必須字段,最多見的可選字段是「最長報文大小 (MSS,Maximum Segment Size)」。
  • 有效數據部分 (Data):這部分也不是必須的,好比在創建和關閉 TCP 鏈接的階段,雙方交換的報文段就只包含 TCP 首部。

1.3 可靠性

咱們都知道 TCP 是具備可靠性的通訊協議,它主要經過如下方式確保可靠性,這裏先了解一下可靠性的原理,其中細節部分後文會講:

  • 合理的數據大小:TCP 發送的數據並非固定的大小,而是會根據實際狀況調整報文段的大小。
  • 檢驗和:發送端按照特定算法計算出 TCP 報文段的檢驗和並存儲在 TCP 首部中的對應字段上,接收端在接收時會以一樣的方式計算校驗和,若是不一致,說明報文段出現錯誤,會將其丟棄。
  • 序號與確認序號:對亂序的數據進行排序後發給應用層,並丟棄重複的數據。
  • 超時重傳機制:當 TCP 發出一個報文段後,它會啓動一個定時器,等待目的端確認收到這個報文段。若是不能及時收到一個確認,將重發這個報文段,後面會細講這個機制。
  • 鏈接管理:也就是三次握手和四次揮手,鏈接的可靠性是總體可靠性的前提,本文第二部分將會詳細介紹鏈接管理的內容。
  • 流量控制:TCP 雙方都有固定大小的緩衝區,流量控制的原理是利用滑動窗口控制數據發送速度,避免緩衝區溢出致使數據丟失。
  • 擁塞控制:TCP 利用慢啓動和擁塞避免等算法實現了擁塞控制。

上面爲你們介紹了 TCP 最重要的三個特色,在本文第一部分的最後,再來看看 TCP 和 UDP 的對比吧。

1.4 TCP 和 UDP 的區別

UDP TCP
是否鏈接 無鏈接 面向鏈接
是否可靠 不可靠,沒有確認機制、流量控制和擁塞控制 可靠,有確認機制、流量控制和擁塞控制
鏈接對象個數 支持一對一,一對多,多對一和多對多交互通訊 只支持一對一通訊
傳輸方式 面向報文 面向字節流
首部開銷 首部開銷小,固定8字節 首部開銷較大,最小20字節,最大60字節
適用場景 適用於實時應用(IP電話、視頻會議、直播等) 適用於要求可靠傳輸的應用,如文件傳輸等

2、TCP 的鏈接控制

2.1 創建鏈接

2.1.1 三次握手

這個問題簡直太經典了,若是你在面試中只被問到了一個關於 TCP 的問題,那大機率就是關於三次握手的問題。TCP 的重要特性之一就是面向鏈接,鏈接雙方在發送數據以前必須經歷握手的階段,那具體的過程是怎樣的呢?先來看圖,你們最好能夠動手簡單畫畫這個圖,固然還有後文四次揮手的圖,幫助加深記憶。

三次握手過程.png

三次握手過程

如圖所示,雙方之間的三個藍色箭頭就表示了三次握手過程當中所發生的數據交換:

  1. 第一次握手:客戶端向服務器發送報文段1,其中的 SYN 標誌位 (前文已經介紹過各類標誌位的做用)的值爲 1,表示這是一個用於請求發起鏈接的報文段,其中的序號字段 (Sequence Number,圖中簡寫爲seq)被設置爲初始序號x (Initial Sequence Number,ISN),TCP 鏈接雙方都可隨機選擇初始序號。發送完報文段1以後,客戶端進入 SYN-SENT 狀態,等待服務器的確認。
  2. 第二次握手:服務器在收到客戶端的鏈接請求後,向客戶端發送報文段2做爲應答,其中 ACK 標誌位設置爲 1,表示對客戶端作出應答,其確認序號字段 (Acknowledgment Number,圖中簡寫爲小寫 ack) 生效,該字段值爲 x + 1,也就是從客戶端收到的報文段的序號加一,表明服務器指望下次收到客戶端的數據的序號。此外,報文段2的 SYN 標誌位也設置爲1,表明這同時也是一個用於發起鏈接的報文段,序號 seq 設置爲服務器初始序號y。發送完報文段2後,服務器進入 SYN-RECEIVED 狀態。
  3. 第三次握手:客戶端在收到報文段2後,向服務器發送報文段3,其 ACK 標誌位爲1,表明對服務器作出應答,確認序號字段 ack 爲 y + 1,序號字段 seq 爲 x + 1。此報文段發送完畢後,雙方都進入 ESTABLISHED 狀態,表示鏈接已創建。

常見面試題 1: TCP 創建鏈接爲何要三次握手而不是兩次?

答:網上大多數資料對這個問題的回答只有簡單的一句:防止已過時的鏈接請求報文忽然又傳送到服務器,於是產生錯誤,這既不夠全面也不夠具體。下面給出比較詳細而全面的回答:

  1. 防止已過時的鏈接請求報文忽然又傳送到服務器,於是產生錯誤

    在雙方兩次握手便可創建鏈接的狀況下,假設客戶端發送 A 報文段請求創建鏈接,因爲網絡緣由形成 A 暫時沒法到達服務器,服務器接收不到請求報文段就不會返回確認報文段,客戶端在長時間得不到應答的狀況下從新發送請求報文段 B,此次 B 順利到達服務器,服務器隨即返回確認報文並進入 ESTABLISHED 狀態,客戶端在收到 確認報文後也進入 ESTABLISHED 狀態,雙方創建鏈接並傳輸數據,以後正常斷開鏈接。此時姍姍來遲的 A 報文段纔到達服務器,服務器隨即返回確認報文並進入 ESTABLISHED 狀態,可是已經進入 CLOSED 狀態的客戶端沒法再接受確認報文段,更沒法進入 ESTABLISHED 狀態,這將致使服務器長時間單方面等待,形成資源浪費。

  2. 三次握手才能讓雙方均確認本身和對方的發送和接收能力都正常

    第一次握手:客戶端只是發送處請求報文段,什麼都沒法確認,而服務器能夠確認本身的接收能力和對方的發送能力正常;

    第二次握手:客戶端能夠確認本身發送能力和接收能力正常,對方發送能力和接收能力正常;

    第三次握手:服務器能夠確認本身發送能力和接收能力正常,對方發送能力和接收能力正常;

    可見三次握手才能讓雙方都確認本身和對方的發送和接收能力所有正常,這樣就能夠愉快地進行通訊了。

  3. 告知對方本身的初始序號值,並確認收到對方的初始序號值

    TCP 實現了可靠的數據傳輸,緣由之一就是 TCP 報文段中維護了序號字段和確認序號字段,也就是圖中的 seq 和 ack,經過這兩個字段雙方均可以知道在本身發出的數據中,哪些是已經被對方確認接收的。這兩個字段的值會在初始序號值得基礎遞增,若是是兩次握手,只有發起方的初始序號能夠獲得確認,而另外一方的初始序號則得不到確認。

常見面試題2: TCP 創建鏈接爲何要三次握手而不是四次?

答:相比上個問題而言,這個問題就簡單多了。由於三次握手已經能夠確認雙方的發送接收能力正常,雙方都知道彼此已經準備好,並且也能夠完成對雙方初始序號值得確認,也就無需再第四次握手了。

常見面試題3: 有一種網絡攻擊是利用了 TCP 創建鏈接機制的漏洞,你瞭解嗎?這個問題怎麼解決?

答:在三次握手過程當中,服務器在收到了客戶端的 SYN 報文段後,會分配並初始化鏈接變量和緩存,並向客戶端發送 SYN + ACK 報文段,這至關因而打開了一個「半開鏈接 (half-open connection)」,會消耗服務器資源。若是客戶端正常返回了 ACK 報文段,那麼雙方能夠正常創建鏈接,不然,服務器在等待一分鐘後會終止這個「半開鏈接」並回收資源。這樣的機制爲 SYN洪泛攻擊 (SYN flood attack)提供了機會,這是一種經典的 DoS攻擊 (Denial of Service,拒絕服務攻擊),所謂的拒絕服務攻擊就是經過進行攻擊,使受害主機或網絡不能提供良好的服務,從而間接達到攻擊的目的。在 SYN 洪泛攻擊中,攻擊者發送大量的 SYN 報文段到服務器請求創建鏈接,可是卻不進行第三次握手,這會致使服務器打開大量的半開鏈接,消耗大量的資源,最終沒法進行正常的服務。

解決方法:SYN Cookies,如今大多數主流操做系統都有這種防護系統。SYN Cookies 是對 TCP 服務器端的三次握手作一些修改,專門用來防範 SYN 洪泛攻擊的一種手段。它的原理是,在服務器接收到 SYN 報文段並返回 SYN + ACK 報文段時,再也不打開一個半開鏈接,也不分配資源,而是根據這個 SYN 報文段的重要信息 (包括源和目的 IP 地址,端口號可一個祕密數),利用特定散列函數計算出一個 cookie 值。這個 cookie 做爲將要返回的SYN + ACK 報文段的初始序列號(ISN)。當客戶端返回一個 ACK 報文段時,服務器根據首部字段信息計算 cookie,與返回的確認序號(初始序列號 + 1)進行對比,若是相同,則是一個正常鏈接,而後分配資源並創建鏈接,不然拒絕創建鏈接。

2.2.2 同時打開

這是 TCP 創建鏈接的特殊狀況,有時會出現兩臺機器同時執行主動打開的狀況,不過幾率很是小,這種狀況你們僅做了解便可。在這種狀況下就無所謂發送方和接收方了,雙放均可以稱爲客戶端和服務器,同時打開的過程以下:

同時打開.png

同時打開的過程

如圖所示,雙方在同一時刻發送 SYN 報文段,並進入 SYN-SENT 狀態,在收到 SYN 後,狀態變爲 SYN-RECEIVED,同時它們都再發送一個 SYN + ACK 的報文段,狀態都變爲 ESTABLISHED,鏈接成功創建。在此過程當中雙方一共交換了4個報文段,比三次握手多一個。

2.2 關閉鏈接

2.2.1 四次揮手

創建一個鏈接須要三次握手,而終止一個鏈接要通過 4次握手。這由 TCP 的半關閉( half-close) 形成的。既然一個 TCP 鏈接是全雙工 (即數據在兩個方向上能同時傳遞), 所以每一個方向必須單獨地進行關閉。這原則就是當一方完成它的數據發送任務後就能發送一個 FIN 來終止這個方向鏈接。當一端收到一個 FIN,它必須通知應用層另外一端已經終止了數據傳送。理論上客戶端和服務器均可以發起主動關閉,可是更多的狀況下是客戶端主動發起。

四次揮手過程.png

四次揮手過程

四次揮手詳細過程以下:

  1. 客戶端發送關閉鏈接的報文段,FIN 標誌位1,請求關閉鏈接,並中止發送數據。序號字段 seq = x (等於以前發送的全部數據的最後一個字節的序號加一),而後客戶端會進入 FIN-WAIT-1 狀態,等待來自服務器的確認報文。
  2. 服務器收到 FIN 報文後,發回確認報文,ACK = 1, ack = x + 1,並帶上本身的序號 seq = y,而後服務器就進入 CLOSE-WAIT 狀態。服務器還會通知上層的應用程序對方已經釋放鏈接,此時 TCP 處於半關閉狀態,也就是說客戶端已經沒有數據要發送了,可是服務器還能夠發送數據,客戶端也還可以接收。
  3. 客戶端收到服務器的 ACK 報文段後隨即進入 FIN-WAIT-2 狀態,此時還能收到來自服務器的數據,直到收到 FIN 報文段。
  4. 服務器發送完全部數據後,會向客戶端發送 FIN 報文段,各字段值如圖所示,隨後服務器進入 LAST-ACK 狀態,等待來自客戶端的確認報文段。
  5. 客戶端收到來自服務器的 FIN 報文段後,向服務器發送 ACK 報文,隨後進入 TIME-WAIT 狀態,等待 2MSL(2 * Maximum Segment Lifetime,兩倍的報文段最大存活時間) ,這是任何報文段在被丟棄前能在網絡中存在的最長時間,經常使用值有30秒、1分鐘和2分鐘。如無特殊狀況,客戶端會進入 CLOSED 狀態。
  6. 服務器在接收到客戶端的 ACK 報文後會隨即進入 CLOSED 狀態,因爲沒有等待時間,通常而言,服務器比客戶端更早進入 CLOSED 狀態。

常見面試題1: 爲何 TCP 關閉鏈接爲何要四次而不是三次?

答:服務器在收到客戶端的 FIN 報文段後,可能還有一些數據要傳輸,因此不能立刻關閉鏈接,可是會作出應答,返回 ACK 報文段,接下來可能會繼續發送數據,在數據發送完後,服務器會向客戶單發送 FIN 報文,表示數據已經發送完畢,請求關閉鏈接,而後客戶端再作出應答,所以一共須要四次揮手。

常見面試題2: 客戶端爲何須要在 TIME-WAIT 狀態等待 2MSL 時間才能進入 CLOSED 狀態?

答:按照常理,在網絡正常的狀況下,四個報文段發送完後,雙方就能夠關閉鏈接進入 CLOSED 狀態了,可是網絡並不老是可靠的,若是客戶端發送的 ACK 報文段丟失,服務器在接收不到 ACK 的狀況下會一直重發 FIN 報文段,這顯然不是咱們想要的。所以客戶端爲了確保服務器收到了 ACK,會設置一個定時器,並在 TIME-WAIT 狀態等待 2MSL 的時間,若是在此期間又收到了來自服務器的 FIN 報文段,那麼客戶端會從新設置計時器並再次等待 2MSL 的時間,若是在這段時間內沒有收到來自服務器的 FIN 報文,那就說明服務器已經成功收到了 ACK 報文,此時客戶端就能夠進入 CLOSED 狀態了。

2.2.2 同時關閉

以前在介紹 TCP 創建鏈接的時候會有一種特殊狀況,那就是同時打開,與之對應地, TCP 關閉時也會有一種特殊狀況,那就是同時關閉,這種狀況僅做了解便可,流程圖以下:

同時關閉過程.png

同時關閉過程

這種狀況下,雙方應用層同時發出關閉命令,這將致使雙方各發送一個 FIN,兩端均從 ESTABLISHED 變爲 FIN_WAIT_1,兩個 FIN 通過網絡傳送後分別到達另外一端。收到 FIN 後,狀態由 FIN_WAIT_1 變遷到 CLOSING,併發送最後的 ACK,當收到最後的 ACK 時,爲確保對方也收到 ACK,狀態變化爲 TIME_WAIT,並等待 2MSL 時間,若是一切正常,隨後會進入 CLOSED 狀態。

3、TCP 的流量控制與滑動窗口

3.1 什麼是流量控制?

TCP 鏈接雙方的主機都爲該鏈接設置了發送緩存和接收緩存,這些緩存起到了蓄水池的做用,咱們確定不能把上層應用程序發來的數據一古腦兒發送到網絡中,而是利用發送緩存將其緩存起來,而後再按必定的速率經過網絡發送給對方,而接收緩存的做用是把對方傳來的數據先緩存起來,等到己方應用程序有空的時候再來取走數據。示意圖以下:

TCP 緩存.png

TCP 緩存示意圖

在此過程當中,若是接收方應用程序讀取數據的速度小於發送方的數據發送速度,將致使接收方的接收緩存溢出,形成數據丟失,這顯然不是咱們想看到的。所以 TCP 爲應用程序提供了流量控制服務 (flow-control service),以消除發送方使接收方的接收緩存溢出的可能性。簡單來講流量控制的目的就是協調發送方的數據發送速度,使其與接收方的數據處理速度相匹配,避免數據丟失,那麼如何實現流量控制呢?

3.2 早期的流量控制模式——中止-等待模式 (stop-wait)

顧名思義就是發送方在發送一個數據包後就中止發送,等待對方響應 ACK,而後才能繼續發送數據。這種模式的具體實現爲 Positive Acknowledgment With Retransmission (PAR),意爲帶重傳的確定確認協議,其實現方式以下圖所示:

PAR 示意圖.png

PAR 示意圖

這種實現很簡單,發送方在發送數據包 (圖中的msg)時會設置一個計時器,而後等待接收方的 ACK,接收方在收到數據後會返回 ACK 做爲應答,發送方在收到 ACK 後會發送下一個數據包。若是因爲網絡緣由形成數據包或者 ACK 丟失時,計時器會超時,而後發送方會從新發送未被確認的數據包。能夠看到,這種模式雖然能夠確保數據傳輸的可靠性,可是有個致命的缺點,那就是效率過低?若是是你,你會怎麼對這個方案進行優化呢?

既然每次發送只一個數據包效率過低,那就多發送幾個,而後給這些數據包編上號,接收端必須對每個包進行確認,這樣設備 A 一次多發送幾個片斷,而沒必要等候 ACK,同時接收端也要告知它可以收多少,這樣發送端發起來也有個限制,固然還須要保證順序性,不要亂序,對於亂序的情況,咱們能夠容許等待必定狀況下的亂序,好比說先緩存提早到的數據,而後去等待須要的數據,若是必定時間沒來就丟掉亂序的數據,來保證順序性,這樣的話,數據傳輸效率就能夠大大提升。不過 TCP 也沒有采用這種方案,而是在此基礎上實現更加複雜的滑動窗口。

3.3 滑動窗口

首先給你們推薦一個視頻,講得很不錯 https://www.bilibili.com/vide...

咱們能夠把發送方的發送緩存中的字節分爲如下四類,每一個編號對應一個字節:

發送緩存字節 分類.png

發送緩存中的字節分類
  1. 第一類:已發送且已確認,這些數據已經發送成功並已經被確認的數據,好比圖中的前31個bytes,這些數據其實的位置是在窗口以外了,下一步將被移出發送緩存。窗口內順序最低的字節被確認以後,窗口左邊界會向右移動,稱爲窗口合攏。
  2. 第二類:已發送但未收到確認,這部分數據已經被髮送出去,可是尚未收到接收端的 ACK,認爲並無完成發送,這部分數據屬於窗口內的數據。
  3. 第三類:未發送可是接收方已經準備好接收,這部分是儘快發送的數據,這部分數據已經被加載到緩存中,也在發送窗口中,正在等待發送,其實這個窗口是徹底有接收方告知的,接收方告知當前能夠接受這些數據,因此發送方須要儘快的發送。
  4. 第四類:未發送且接收方未準備好接收,這些數據屬於未發送,同時接收端也不容許發送的,由於這些數據已經超出了發送端所接收的範圍。
3.3.1 發送窗口和接收窗口

發送窗口.png

發送窗口

發送窗口:圖中的黑色框就是發送方的發送窗口,其大小由兩個因素決定:一、接收方的提供的窗口大小 (TCP 報文段首部中的 window 字段),發送方在三次握手階段首次獲得這個值,以後的通訊過程當中接收方會根據本身的可用緩存對這個值進行動態調整;二、發送方會根據網絡狀況維護一個擁塞窗口變量 (後文介紹)。發送窗口的大小取這兩個值的最小值。對於發送方來講,發送窗口分爲兩部分,分別是已經發送的部分(已經發送了,可是沒有收到ACK)和可用窗口,接收端容許發送可是沒有發送的那部分稱爲可用窗口。

接收窗口:對於接收端也是有一個接收窗口的,相似發送端,接收端的數據有3個分類,由於接收端並不須要等待ACK因此它沒有相似的接收並確認了的分類,狀況以下

  1. Received and ACK Not Send to Process:這部分數據屬於接收了數據可是尚未被上層的應用程序接收;
  2. Received Not ACK: 已經接收,可是尚未回覆 ACK;
  3. Not Received:有空位,尚未被接收的數據。
3.3.2 滑動窗口是如何滑動的?

滑動窗口流程.png

滑動窗口的滑動過程

累積確認概念:TCP 並非每個報文段都會回覆一個 ACK ,可能會對兩個報文段發送一個ACK,也可能會對多個報文段發送 1 個 ACK,這稱爲累積確認。好比說發送方有 1/2/3 3 個報文段,先發送了2,3 兩個報文段,可是接收方指望收到1報文段,這個時候 2/3 報文段就只能放在緩存中等待報文1的空洞被填上,若是報文段1一直不來,報文2/3也將被丟棄,若是報文1來了,那麼會發送一個 ACK 對第3個報文段進行確認,就表明對這三個報文段所有進行了確認。

下面舉例說明一下窗口滑動的過程:

  1. 在握手過程當中,接收方通告的窗口大小爲20字節,因此發送方將發送窗口大小設置爲20字節。
  2. 從圖中的"上一個發送窗口的位置"(灰色虛線框)提及, 32-51號字節剛好處於發送窗口中,剛好20個字節,假設 TCP 將其分爲 4 個報文段進行發送,每一個報文段 5 個字節數據,分別記爲 seg1 32-36, seg2 37-41, seg3 42-46, seg4 47-51。
  3. TCP 將有序發送 seg一、seg二、seg3和seg4四個報文段,若是這四個報文段都順利到達接收方 (圖中並非這樣),接收方將發回一個累積確認的 ACK 報文段,其中 ack = 52,表明但願收到下一個報文段的起始字節編號,報文段中也會繼續通告窗口大小,若是仍是20字節,那麼發送方的窗口將總體向右移動20字節,若是通告的窗口值變小,好比變成15,那麼發送窗口左邊界移動20字節,右邊界移動15字節。
  4. 若是在發送過程當中 seg2 報文段丟失,而其餘三個報文段正常到達接收方,那麼接收方會現接受這三個報文段,而後返回 ACK 報文段,ack = 37,表示但願收到的下一個報文段的起始字節號爲37,也就是seg2報文段。若是通告窗口值未發生變化,發送方在收到 ACK 後會將窗口總體右移5個字節,也就變成了圖中的位置。
  5. 因爲 seg2 還未收到 ACK,當重傳計時器超時後,發送方會從新發送 seg2,此時52-56號字節又落到了發送窗口中,TCP 將其封裝成 報文段進行發送,若是接收方所有順利收到,會返回一個累積確認的 ACK,ack = 57,表示但願收到的喜好個報文段的起始字節號爲57。

接下來就是重複上述過程,直到 TCP 字節流的全部數據發送完畢。在這個過程當中,接收方會根據本身接收緩存的剩餘空間動態調整窗口值,對發送方進行流量控制。文字描述可能不夠直觀,你們能夠參考上文推薦的視頻。另外推薦一個動圖演示的網站 動畫地址,能夠觀看滑動窗口的動態效果,以下圖 (此演示未考慮丟包的狀況):

滑動窗口動畫效果

4、TCP 的擁塞控制

4.1 什麼是擁塞控制?

當數據從一個大的管道 (好比一個快速局域網)向一個較小的管道 (好比較慢的廣域網)發送的時候就會發生擁塞,還有一種狀況就是當多個輸入流到達一個路由器,而路由器的輸出流小於這些輸入流的總和時,也會發生擁塞。舉個例子就好理解了,第一種狀況就好像源源不斷的車流從八車道進入四車道,若是不進行控制,必然形成道路擁堵;第二種狀況相似於不少車輛匯入十字路口,若是進的速度大於出的速度,再不加以控制,必然也會形成擁堵。因而 TCP 提供了響應的機制來應對這種狀況,也就是 TCP 的擁塞控制。

4.2 如何實現擁塞控制?

TCP 一共使用了四種算法來實現擁塞控制:一、慢開始 (slow-start);二、擁塞避免 (congestion avoidance);三、快速重傳 (fast retransmit);四、快速恢復 (fast recovery)。

這裏先介紹一下擁塞窗口 (congestion window,簡寫爲 cwnd)的概念:擁塞窗口是由發送方根據網絡情況維護的一個變量,用於控制本身的數據發送速率。前文提到了發送方的發送窗口受兩個變量約束,一是接收方通告的窗口大小值,二就是發送方自身的擁塞窗口,實際的發送窗口大小取兩者最小值。

4.2.1 慢開始(慢啓動)

在早期的 TCP Tahoe 版本中,只用到了前兩種算法,如圖所示:

慢開始和擁塞避免.png

慢開始和擁塞避免

如圖所示,在剛開始,TCP 採用慢開始算法。慢開始不是指擁塞窗口的增加速度慢(增加速度是指數增加,很是快),而是指 TCP 開始發送設置 cwnd=1。思路就是不要一開始就發送大量的數據,先探測一下網絡的擁塞程度,也就是說由小到大 逐漸增長擁塞窗口的大小。這裏用報文段的個數的擁塞窗口大小舉例說明慢啓動算法,實時擁塞窗口大小是以字節爲單位的。爲了防止 cwnd 增加過大引發網絡擁塞,設置一個慢開始門限(slow start threshold,簡寫爲 ssthresh) ,

當cnwd < ssthresh,使用慢開始算法
當 cnwd = ssthresh,既可以使用慢開始算法,也可使用擁塞避免算法
當 cnwd > ssthresh,使用擁塞避免算法

4.2.2 擁塞避免

當擁塞窗口大小達到初始 ssthresh 值時,轉而採用擁塞避免算法。擁塞避免並不是徹底可以避免擁塞,是說在擁塞避免階段將擁塞窗口控制爲按線性規律增加,使網絡比較不容易出現擁塞,思路:讓擁塞窗口 cwnd 緩慢地增大,即每通過一個往返時間 RTT 就把發送方的擁塞窗口加一。不管是在慢開始階段仍是在擁塞避免階段,只要發送方判斷網絡出現擁塞(其根據就是沒有收到確認,雖然沒有收到確承認能是其餘緣由的分組丟失,可是由於沒法斷定,因此都當作擁塞來處理),就把慢開始門限設置爲出現擁塞時的發送窗口大小的一半。而後把擁塞窗口設置爲 1,執行慢開始算法。

4.2.3 快速重傳

TCP Reno.png

TCP Reno 應用了四種算法

有時候的發送方未收到某個報文段的確認也並必定就說明必定是出現了網絡擁塞,也多是其餘緣由,因此直接執行慢開始算法會影響總體效率,後來的 TCP Reno 版本解決了這一問題,那就是採用快速重傳和快速恢復算法。

快速重傳要求接收方在收到一個失序的報文段後就當即發出重複確認(爲的是使發送方及早知道有報文段沒有到達對方),而不要等到本身發送數據時捎帶確認。快重傳算法規定,發送方只要一連收到三個重複確認就應當當即重傳對方還沒有收到的報文段,而沒必要繼續等待設置的重傳計時器時間到期。因爲不須要等待設置的重傳計時器到期,能儘早重傳未被確認的報文段,能提升整個網絡的吞吐量。

4.2.4 快速恢復

當發送方連續收到三個重複確認時,就執行「乘法減少」算法,把 ssthresh 門限減半。 可是接下去並不執行慢開始算法。考慮到若是網絡出現擁塞的話就不會收到好幾個重複的確認,因此發送方如今認爲網絡可能沒有出現擁塞。因此此時不執行慢開始算法,而是將 cwnd 設置爲 ssthresh 的大小, 而後執行擁塞避免算法。

5、TCP 粘包與拆包

5.1 TCP 粘包和拆包的緣由

咱們知道 TCP 是以字節流的方式傳輸數據,傳輸的最小單位爲一個報文段(segment)。TCP 首部 中有個選項 (Options)的字段,常見的選項爲 MSS (Maximum Segment Size最大消息長度),它是收發雙方協商通訊時每個報文段所能承載的最大有效數據的長度。數據鏈路層每次傳輸的數據有個最大限制MTU (Maximum Transmission Unit),通常是1500比特,超過這個量要分紅多個報文段,MSS 則是這個最大限制減去 TCP 的首部,光是要傳輸的數據的大小,通常爲1460比特。換算成字節,也就是180多字節。

MSS = MTU - Header

TCP 爲提升性能,發送端會將須要發送的數據發送到發送緩存,等待緩存滿了以後,再將緩存中的數據發送到接收方。同理,接收方也有接收緩存這樣的機制,來接收數據。

上面這些是發生 TCP 粘包和拆包的前提,下面是具體的緣由:

  1. 要發送的數據大於TCP發送緩衝區剩餘空間大小,將會發生拆包。
  2. 待發送數據大於MSS(最大報文長度),TCP在傳輸前將進行拆包。
  3. 應用程序寫入數據小於剩餘緩存大小,網卡將應用屢次寫入的數據先緩存起來,而後一塊兒發送到網絡上,這將會發生粘包。
  4. 接收數據端的應用層沒有及時讀取接收緩存中的數據,將發生粘包。

5.2 TCP 粘包和拆包的解決方案

  1. 設置定長消息,服務端每次讀取既定長度的內容做爲一條完整消息。
  2. 設置消息邊界,數據結尾尾增長特殊字符分割。
  3. 使用帶消息頭的協議,消息頭存儲消息開始標識及消息長度信息,接收方獲取消息頭的時候解析出消息長度,而後向後讀取該長度的內容。

6、結語

本文爲你們梳理了 TCP 的核心概念和原理,也分享了一些高頻面試題,但願對你有幫助。儘管文章字數超過了一萬字,可是 TCP 的不少細節還遠遠沒有介紹到,若是想進一步瞭解,能夠參考一下下面參考資料中提到的幾本書。

參考資料:

  1. 《TCP/IP詳解卷一:協議》
  2. 《計算機網絡自頂向下方法》
  3. 《TCP/IP Guide》
  4. TCP-IP詳解:滑動窗口(Sliding Window)
相關文章
相關標籤/搜索