TCP協議的這些那些事兒-7000字超全圖文並茂

寫在前面:這裏是小王成長日誌,一名在校大學生,想在學習之餘將自己的學習筆記分享出來,記錄自己的成長軌跡,幫助可能需要的人。歡迎關注與留言。

0. 導入-TCP概述

  1. TCP 是因特網運輸層的面向連接的可靠的運輸協議,與之相對的還有並不可靠的UDP協議,可以看這篇博文進行了解-沒搞清運輸層的UDP協議? -哎呀, 早來這看就好了啊

  2. TCP 依賴於許多基本原理,其中包括差錯檢測、重傳、累積確認、定時器以及用於序號和確認號的首部字段 。

  3. TCP的產生歷史

  • 在 20 世紀 70 年代早期,分組交換網開始飛速增長,而因特網的前身 ARPAnet 也只是當時衆多分組交換網中的一個 。 這些網絡都有它們各自的協議 ,彼此之間並不互通.
  • Vinton Cerf 和 Robert Kahn 這兩個研究人員認識到互聯這些網絡的重要性,於是就發明了溝通網絡的 TCP/IP 協議

1. TCP連接的建立與數據傳輸

  • 在TCP協議中,兩個應用進程之間發送數據之前必須先建立連接(互相發送一些報文段初始化與 TCP 連接相關的許多 TCP 狀態變量-就是"握手"的階段)

  • 在TCP連接中,其連接狀態完全保留在兩個端系統之中.所以對於兩個端系統之間的網絡元素(路由器和鏈路層交換機)而言,他們是看不到連接的,他們也不會去維護連接狀態。

  • 通過一條TCP連接,數據可以雙向流動。這就叫做全雙工服務,即可以雙向傳輸數據。

TCP連接建立流程

  • 首先,我們定義發起連接的這個進程被稱爲客戶進程,而另一個進程被稱爲服務器進程
  • 客戶進程通知客戶運輸層,其想與服務器上的一個進程建立一條連接
    • 客戶首先發送一個特殊的 TCP 報文段
    • 服務器用另一個特殊的 TCP 報文段來響應
    • 最後,客戶再用第三個特殊報文段作爲響應

建立TCP連接後

  • 兩臺端系統之間一旦建立起一條 TCP 連接,兩個應用進程之間就可以相互發送數據了

    • 客戶進程通過套接字(該進程之門)傳遞數據流 。 數據一旦通過該門,它就由客戶中運行的 TCP 控制了
    • TCP 將這些數據引導到該連接的發送緩存 (send buffer) 裏,發送緩存是在三次握手初期設置的緩存之一。
  • 接下來 TCP 就會不時從發送緩存裏取出一塊數據

    • 取出數據的數量受限於最大報文段長度 (Maximum Segmenl Size, MSS)

    • MSS 通常根據最初確定的由本地發送主機發送的最大鏈路層幀長度(即所謂的最大傳輸單元 (Maximum Transmission Unit , MTU)) 來設置。設置該 MSS 要保證一個 TCP 報文段(當封裝在一個 IP 數據報中)加上 TCP/IP 首部長度(通常 40 字節)將適合單個鏈路層幀。以太網和 PPP 鏈路層協議都具有 1500 字節的 MTU ,因此 MSS 的典型值爲 1460 字節 。

      • 最大傳輸單元(Maximum Transmission Unit,MTU)用來通知對方所能接受數據服務單元的最大尺寸,說明發送方能夠接受的有效載荷大小。
      • 是包或幀的最大長度,一般以字節記。如果MTU過大,在碰到路由器時會被拒絕轉發,因爲它不能處理過大的包。如果太小,因爲協議一定要在包(或幀)上加上包頭,那實際傳送的數據量就會過小,這樣也划不來。大部分操作系統會提供給用戶一個默認值,該值一般對用戶是比較合適的。
    • 當 TCP 發送一個大文件,例如某 Web 頁面上的一個圖像時, TCP 通常是將該文件劃分成長度爲 MSS 的若干塊(最 32比特後一塊除外,它通常小於 MSS) 。

    • MSS 是指在報文段裏應用層數據的最大長度,而不是指包括 TCP 首部的 TCP 報文段的最大長度。

  • TCP 爲每塊客戶數據配上一個 TCP 首部,從而形成多個 TCP 報文段 (TCP segment) ,這些報文段被下傳給網絡層

  • 圖例

在這裏插入圖片描述

2. TCP報文段結構

  • TCP報文段由首部字段和一個數據字段組成

  • 結構圖例

在這裏插入圖片描述

首部字段

  1. 源端口號

  2. 目的端口號

這兩個端口號都是用於多路分解和多路複用的,即報文段準確送達指定套接字,具體可以看我之前的博文-一文帶你看懂多路複用與多路分解

  1. 檢驗和字段

用於檢測在傳輸過程中報文段中的數據是否發生比特錯誤,具體可看我之前的博文-沒搞清運輸層的UDP協議? -哎呀, 早來這看就好了啊,在這篇博文的第四個副標題下提到了關於檢驗和的出現原因以及計算方式。

  1. 32 比特的序號字段 (sequence number field)

    • 序號依賴於傳輸的字節流,而非傳送的報文段

    • 序號受MMS(最大報文段長度)的限制,例如現在有一個50 000字節的文件帶傳送,MMS爲1000字節,則TCP會將其分爲50個報文段,其中第一個報文段假設初始序號爲0(但不一定是0,是隨機選擇的一個數以防止跟其他程序的報文段序號重複),則第二個報文段序號爲1000(0+MMS * 1),第三個報文段序號爲2000

    • 序號的選擇通常在確定連接時由雙方確定,均可隨機選擇

    • 圖例

在這裏插入圖片描述

  1. 32 比特的確認號字段( acknowledgment number field)

    • 由於TCP的全雙工特性,兩個端系統之間互相都會發送數據(報文段)

    • 假設主機 A 填充進報文段的確認號爲N

      • 累積確認:A已經收到序號爲(N-1)及之前的所有字節
      • A接下來希望收到來自B的序號爲N的報文段
      • 確認號只會有一個,即流中第一個丟失字節(或者最後一個確認的字節後面一個)
  2. 16 比特的接收窗口字段 (receive window field)

  3. 4 比特的首部長度字段 (header length field)

  4. 可選與變長的選項字段 ( options field)

    • 用於發送方與接收方協商最大報文段長度 (MSS) 時

    • 或在高速網絡環境下用作窗口調節因子時使用 。

  5. 6 比特的標誌字段 (fLag field)

Telnet: 序號和確認號的一個學習案例

  1. Telnet簡介

    • 假設主機 A 發起一個與主機 B 的 Telnet 會話 。
    • (在客戶端的)用戶鍵人的每個字符都會被髮送至遠程主機;遠程主機將回送每個字符的副本給客戶,並將這些字符顯示在 Telnet 用戶的屏幕上。
    • 這種"回顯" (echo back) 用於確保由 TelneL 用戶發送的字符已經被遠程主機收到並在遠程站點上得到處理。
    • 因此,在從用戶擊鍵到字符被顯示在用戶屏幕上這段時間內,每個字符在網絡中傳輸了兩次 。
  2. 圖解

在這裏插入圖片描述

  1. 第一個報文段

    • 序號爲42

      • 在建立連接時雙方確定主機A的初始序號爲42,所以主機A發送的第一個報文段序號就是42
    • 確認號爲79

      • 在建立連接時雙方確定主機B的初始序號爲79,所以主機A發送的第一個確認號就是79,表示接下來希望收到來自B的序號爲79的報文段
    • 數據字段爲字符C

      • 傳輸的字符
  2. 第二個報文段

    • 序號爲79

      • 在建立連接時雙方確定主機B的初始序號爲79,所以主機B發送的第一個報文段序號就是79
    • 確認號爲43

    • 1.已經收到序號爲42及之前(在這裏沒有之前)的報文段(字節)

      • 2.接下來希望收到來自A的序號爲43的報文段
    • 數據字段爲字符C

      • 回顯字符
  3. 第三個報文段

    • 序號爲43

      • 表示這個由A發送的報文段序號爲43
    • 確認號爲80

      • 已經收到序號爲79及之前(在這裏沒有之前)的報文段(字節)
      • 接下來希望收到來自B的序號爲80的報文段(即是後面沒了,但是他還是需要填入一個序號)

3. 往返時間的估計與超時

  1. 往返時間的估計

    • RTT(Round-Trip Time),表示爲SampleRTT,在TCP協議中只會被在某個時刻測量一次,而非對所有報文段進行測量,且對於重傳的報文段絕不做測量

    • 由於路由器和端系統的變化,我們測量的SampleRTT明顯都會發生波動,是非典型的,我們要對SampleRTT取平均值

    • 每當獲得一個新的SampleRTT,就採用如下公式更新EstimatedRTT

    • EstÌmatedRTT = (1 -α) . EstimatedRTT +α. SampleRTT

      • 指數加權移動平均 (Exponential Weighted Moving Average , EWMA)
      • 在 EWMA中的"指數"一詞看起來是指一個給定的 SampleRTT 的權值在更新的過程中呈指數型快速衰減 。
    • 可見 EstimatedRTT 的新值是由以前的 EstimatedRTT 值與 SampleRTT 新值加權組合而成的 。

    • 在[RFC 6298]中給出的 α 參考值是α=0.125 (即 1/8)

  2. 往返偏差的計算

    • 測量RTT的變化,DevRTT表示RTT 偏差
    • 公式: DevRTT = (1 -β) . DevRTT +β. I SampleRTT 一 EstimatedRTT I
    • β 的推薦值爲 0.25
  3. 設置和管理重傳超時間隔

    • 很明顯我們的超時間隔應大於我們的EstimatedRTT,但也不能大太多以免造成時延過大
    • 公式:TimeoutInterval =EstimatedRTT+4 * DevRTT

4. 可靠數據傳輸

  1. 我們先討論一個高度簡化版本-其只用超時恢復報文段的缺失。

    1.1 首先我們看看3 個與發送和重傳有關的主要事件

    • 事件1:從上層應用程序接收數據

      • 收到數據
      • 封裝進報文段
      • 賦予序號
      • 交給IP層
      • 定時器啓動
    • 事件2:定時器超時

      • 定時超時重傳則相應報文段

      • 然後重啓定時器

    • 事件3:收到 ACK

      • 情況1:收到的序號等於sendbase

        • 累積確認

        • 若有還未確認的報文段,則重啓定時器

      • 情況2:收到的序號大於sendbase

        • 累計確認

        • 若有還未確認的報文段,則重啓定時器

    1.2 但是這也可能產生一些有趣的情況,考慮下列場景:

    • 接收方發送的確認報文在超時間隔之後纔到達,發送方由於超時未接收到確認報文導致重傳

    • 接收方發送的確認報文丟失,發送方由於超時未接收到確認報文導致重傳

    1.3 超時間隔加倍

    • 對於重傳的數據報,每次超時間都會加倍
    • 當然對於由於從上層接收到數據和收到ACK而啓動的定時器還是正常的Timeoutlnterval 由最近的 EstimaledRTT 值與 DevRTT 值推算得到 。
    • 這是一種形式受限的擁塞控制
  2. 現在我們來進行一個更全面的描述-超時機制加上冗餘確認技術

    • 對於超時重傳,存在的問題可能是超時週期過長,會增加端到端的時延

    • 因此採用發送冗餘ACK的技術

      • 重複發送某個報文段(最後確認字節)的ACK,表示後面的報文可能丟失了
      • 允許接收方不丟棄失序報文段
      • 一般收到三個冗餘ACK,TCPP就執行快速重傳,即立即發送丟失的報文段
    • 接收方會遇到的四種情況

      • 期望序號N的報文按序達到 - ACK延遲最多500ms,等待下一個報文段的是否在時間內到達,沒到達則發送對於N的ACK

      • 具有所期望序號的按序報文段到達,另一個按序報文段等待ACK傳輸 - 對應第一種情況,直接發送ACK,以確認兩個按序報文段

      • 比期望序號大的失序報文段到達,檢測出間隔 - 立即發送冗餘ACK

      • 能部分或完全填充接收數據間隔的報文段到達 - 立即發送最大確認ACK

  3. TCP是選擇回退N步還是選擇重傳’?(若還對回退N步以及選擇重傳比較模糊可以看我之前的博文-一文帶你看流水線協議,回退N步以及選擇重傳)

  • 想想TCP是GBN還是SR?

  • TCP 發送方僅需維持已發送過但未被確認的字節的最小序號( SendBase) 和下一個要發送的字節的序號( NextSeqNum) 。 在這種意義下, TCP 看起來更像一個 GBN 風格的協議。

  • 但是TCP又與GBN有一些區別

    • 許多TCP 實現會將正確接收但失序的報文段緩存起來 [Stevens 1994 J 。
  • 對 TCP 提出的一種修改意見是所謂的選擇確認 (selective acknowledgment) [RFC 2018J ,它允許 TCP 接收方有選擇地確認失序報文段,而不是累積地確認最後一個正確接收的有序報文段 。 當將該機制與選擇重傳機制結合起來使用時(即跳過重傳那些已被接收方選擇性地確認過的報文段), TCP 看起來就很像我們通常的 SR 協議 。

  • 因此, TCP 的差錯恢復機制也許最好被分類爲 GBN 協議與 SR 協議的混合體 。

5. 流量控制

流量控制服務的來源

  • 我們講過TCP每一側主機在建立連接時都會設置接收緩存
  • 當TCP連接收到正確,按序的字節後,就將數據放入接收緩存
  • 相關應用進程可以從緩存中讀取數據,但不一定是剛到就去讀
  • 如果進程讀取數據的速度小於發送的速度就會造成接收緩存溢出
  • 流量控制服務( flow- control service) 就是TCP爲其應用程序提供的消除發送方使接收方緩存溢出的可能性的服務

什麼是流量控制服務

  • 流量控制服務其實就是一個速度匹配服務,用來匹配發送方的發送速率與接收方應用程序的讀取速率
  • 區分於擁塞控制,兩者起因不同,擁塞控制是因爲網絡的擁塞

流量控制服務的實現

  • TCP 通過讓發送方維護一個稱爲接收窗口 (receive window) 的變量來提供流量控制,即接收窗口用於給發送方一個指示一一該接收方還有多少可用的緩存空間

  • 舉例實現

    • 接收窗口用於給發送方一個指示一一該接收方還有多少可用的緩存空間

    • 主機 B 接收緩存爲 RcvBuffer

    • 定義變量

      • LastByteRead

        • 主機 B 上的應用進程從緩存讀出的數據流的最後一個字節的編號 。
      • LastByteRcvd

        • 從網絡中到達的並且已放人主機 B 接收緩存巾的數據流的最後 一個字節的編號。
      • " LasLByteRcvd - LastByteRead ~ RcvBuffer "明顯必須成立

    • 接收窗口用 rwnd 表示

    • 接收窗口rwnd 必須滿足

      • rwnd = RcvBuffer - [LastByteRcvd - LastßyteRead ]
  • 圖例

在這裏插入圖片描述

  • 思考一個問題

    • 假設主機 B 的接收緩存已經存滿,使得 rwnd =0 。 在將 rwnd =0 通告給主機 A 之後,還要假設主機 B 沒有任何數據要發給主機 A 。 此時,考慮會發生什麼情況 。
    • 因爲主機 B 上的應用進程將緩存清空, TCP 並不向主機 A 發送帶有 rwnd 新值的新報文段;
    • 事實上 , TCP 僅當在它有數據或有確認要發時纔會發送報文段給主機 A 。 這樣,主機 A 不可能知道主機 B 的接收緩存已經有新的空間了,即主機 A 被阻塞而不能再發送數據!
    • 爲了解決這個問題, TCP 規範中要求:當主機 B 的接收窗口爲 0 時,主機 A 繼續發送只有一個字節數據的報文段 。 這些報文段將會被接收方確認 。 最終緩存將開始清空,並且確認報文裏將包含 一個非 0 的rwnd 值 。

6. TCP連接管理

TCP連接的建立-三次握手

  • 第一步

    • 客戶端TCP向服務器TCP發送一個不含應用層數據的特殊TCP報文
    • 該報文首部的一個標誌位(SYN)被置爲1,因此該報文被稱爲SYN報文段
    • 客戶隨機選擇一個初始序號置於該報文段的序號字段中
    • 報文段封裝進一個IP數據報中,發送給服務器
  • 第二步

    • 服務器TCP接收到客戶發來的SYN報文後,爲該TCP分配TCP緩存和變量,並向客戶發送允許連接的報文(確認報文)
    • 該允許報文SYN標誌置爲1
    • 該允許報文確認號置爲客戶的初始序號+1
    • 該允許報文攜帶了服務器自己選擇的初始序號
    • 該確認報文也被稱爲SYNACK 報文段 (SYNACK segment)
  • 第三步

    • 客戶收到服務器的SYNACK報文後,給該TCP分配緩存以及變量,並向服務器發送該SYNACK的確認報文
    • 確認報文確認號爲服務器選擇的初始序號
    • 因爲已建立連接,所以標誌SYN置爲0
    • 這段報文可以攜帶應用層數據
  • 圖例

在這裏插入圖片描述

  • 三次握手的原因

    • 確保雙方都已經準備就緒了

TCP連接的拆除-四次揮手

  1. 概述
    • 在連接結束後,主機中的資源(緩存和變量)都將被釋放
    • 客戶首先發送一個特殊報文,該報文標誌FIN置爲1
    • 服務接收到FIN報文後,就像發送方回送一個確認報文
    • 服務器繼續發送自己的數據
    • 服務器向客戶發送自己的FIN報文(在這裏FIN報文還是遵循超時間隔的規則的!,所以可能會多次發送)
    • 客戶接收後向服務器回送FIN的ACK報文
    • 服務器接收到回送的ACK後直接關閉連接
    • 要注意到連接的關閉也可以由服務器發起
  2. 圖例

在這裏插入圖片描述

  1. SYN洪泛攻擊

    • SYN洪泛攻擊是攻擊方大量發送SYN報文而不回覆SYNACK的ACK報文,使服務器爲這些SYN分配資源建立大量半連接.

    • 應對手段

      • 現有的一種有效的防禦系統:SYN cookie

      • 正常的過程, 半連接隊列未滿

        • 服務前端接收到 SYN(seq_a), 判斷隊列有沒有如果沒有滿, 按照正常的處理過程, 將 SYN 放到半連接隊列中, 迴應SYN(seq_b)+ACK(seq_a+1)
        • 客戶端迴應ACK(seq_b+1), 服務端接受到客戶端 ACK, 去檢查半連接隊列裏是否有對應的 SYN, 如果有建立連接, 放到連接完成隊列
      • SYN foold攻擊, 半連接隊列滿

        • 接收到 SYN, 判斷半連接隊列是否滿, 是否開啓了syncookie, 按照syncookie進行接下來處理
        • 將接收到的 SYN 進行算法hash, 將關鍵的字段加密成 服務端迴應的SYN的seq , 發送給客戶端ACK+SYN(此時seq=hash後的而關鍵數值), 並丟棄SYN. (可以類比seq爲base64-encode過程)
        • 如果客戶端是正常的客戶端會對服務器端的SYN(seq)迴應ACK(seq+1)
        • 服務端接收到 ACK(seq+1), 先檢查半連接隊列是否存在該ACK對應的SYN, 如果不存在, 繼續檢查是否開啓了syncookie, 如果開啓了, 就檢查seq是否爲合法cookie, 如果是則對其進行逆運算, 恢復之前SYN, 繼續操作.(可以類比base64-decode)

7.運輸層相關博文


都看到這裏了,各位哥哥姐姐叔叔阿姨給小王點個贊 關個注 留個言吧,和小王一起成長吧,你們的關注是對我最大的支持。
有事沒事進來看看吧 : 小王的博客目錄索引


如果以上內容有任何不準確或遺漏之處,或者你有更好的意見,就在下面留個言讓我知道吧-我會盡我所能來回答。

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