1. 程式人生 > 實用技巧 >TCP 協議簡析

TCP 協議簡析

基礎知識

作為網路基礎知識,相信大家對 TCP/IP 5層協議模型 不會感到陌生:

  • 應用層(應用層,表示層,會話層):具體的應用邏輯
  • 傳輸層:提供程序間的通訊服務
  • 網路層:提供端系統之間的資料透明傳送
  • 鏈路層:資料在具體物理裝置上的表示(裝置驅動、網絡卡)
  • 物理層:具體物理裝置之間的連結(電纜)

其中的 TCP 與 IP 協議是網際網路的基石,理解這兩個協議對於應用開發者與系統管理元來說至關重要。

IP 協議

IP Internet Protocol 協議解決了大規模、異構網路的互聯互通問題:

  1. 將不同物理裝置的幀轉換為統一的 IP 資料報 datagram,實現異構物理裝置間的通訊
  2. 為每個裝置分配一個唯一的邏輯地址,遮蔽實體地址的差異,實現異構物理裝置間的定址

IP 協議提供不可靠 Unreliable、無連線 Connectionless 的資料報傳輸功能:

  • 不可靠:不能保證IP資料報能成功地到達目的地
IP 協議僅保證提供傳儘可能好 Best Effort 的輸服務。

當發生某種錯誤時,如某個路由器暫時用完了緩衝區,IP有一個簡單的錯誤處理演算法:丟棄資料報,然後傳送ICMP訊息報給信源端。

  • 無連線:不維護任何關於後續資料報的狀態資訊
每個資料報的處理是相互獨立的。

如果一信源向相同的信宿傳送兩個連續的資料報(先是A,然後是B),每個資料報都是獨立地進行路由選擇,可能選擇不同的路線,因此B可能在A到達之前先到達。

受限於協議的特性,IP 資料報可能出現 丟包

亂序。因此基於IP協議的網際網路絡必然是一個不可靠的網路。

TCP 協議

TCP Transport Control Protocol 協議是一個面向連線的、可靠的、基於位元組流的傳輸層協議。其主要目的是在不可靠的網際網路絡上提供可靠的端到端位元組流傳輸。

網際網路絡的不同部分可能有截然不同的拓撲結構、頻寬、延遲、資料包大小和其他引數。
TCP的設計目標是能夠動態地適應網際網路絡的這些特性,而且具備面對各種故障時的健壯性。

其核心的功能點可以概括為兩方面:

  • 流量控制:保證資料傳輸效率

    • 資料分片:在傳送端對使用者資料進行分片,在接收端進行重組,由TCP確定分片的大小並控制分片和重組
    • 滑動視窗
      :TCP連線每一方的接收緩衝空間大小都固定,接收端只允許另一端傳送接收端緩衝區所能接納的資料,TCP在滑動視窗的基礎上提供流量控制,防止較快主機致使較慢主機的緩衝區溢位
  • 可靠傳輸:保證資料傳輸正確性

    • 到達確認:接收端接收到分片資料時,根據分片資料序號向傳送端傳送一個確認
    • 超時重發:傳送方在傳送分片時啟動超時定時器,如果在定時器超時之後沒有收到相應的確認,重發分片
    • 失序處理:作為IP資料報來傳輸的TCP分片到達時可能會失序,TCP將對收到的資料進行重新排序,將收到的資料以正確的順序交給應用層
    • 重複處理:作為IP資料報來傳輸的TCP分片會發生重複,TCP的接收端必須丟棄重複的資料
    • 資料校驗:TCP將保持它首部和資料的檢驗和,這是一個端到端的檢驗和,目的是檢測資料在傳輸過程中的任何變化。如果收到分片的檢驗和有差錯,TCP將丟棄這個分片,並不確認收到此報文段導致對端超時並重發

TCP 細節

報文結構

TCP 的基本傳輸單位是報文段 segment,其結構如下:

  • 埠號是 16bit 的無符號數,每個TCP段都包含源端和目的端的埠號,用於尋找發端和收端應用程序。
    這兩個值加上 IP首部中的源端IP地址和目的端IP地址唯一確定一個TCP連線。

  • 序號是 32bit 的無符號數,用來標識由傳送端發出的資料位元組流,它表示在這個報文段中的的第一個資料位元組。 TCP用序號對每個位元組進行計數。

  • 確認序號也是 32bit 的無符號數,用於標識傳送確認的一端所期望收到的下一個序號。確認序號應當是上次已成功收到資料位元組序號加1。

  • 首部長度給出首部中32bit字的數目。需要這個值是因為任選欄位的長度是可變的。然而,沒有任選欄位,正常的長度是20位元組。這個欄位佔4bit,因此TCP最多有60位元組的首部。

  • 特殊標記位。它們中的多個可同時被設定為1。

    • URG 緊急指標
    • ACK 確認序號有效
    • PSH 接收方應該儘快將這個報文段交給應用層
    • RST 重建連線
    • SYN 同步序號用來發起一個連線
    • FIN 發端完成傳送任務
  • 視窗大小是一個16bit欄位,其單位為位元組,因而視窗大小最大為65535位元組。傳送端可以通過這個欄位來宣告接受視窗大小,告知對方自己期望接受的資料量,從而實現流量控制。

  • 選項部分是用於支援 TCP 的一些擴充套件功能:資料分片、視窗放大、時間戳

資料分片與重組

路徑MTU

目前最常見的區域網協是乙太網Ethernet,其網路拓撲為匯流排型拓撲,即多個計算機共享同個通道。計算機要傳送資訊時,會通過共享線路將資訊廣播到其他計算機上。當多個計算機同時傳送訊息時,彼此之間會有干擾,導致資料傳送失敗。

因此乙太網引入了一個衝突檢測collision detection的功能,保證通過時刻只有一個計算機在網路上傳送訊息。

這造成了兩個問題:

  • 每次傳送都要執行衝突檢測,需要額外的通訊開銷(資料封裝、裝置資源)。如果每次只傳輸一個位元組,傳輸效能會十分低下,因此應該儘可能減少傳輸次數,即一次儘可能傳輸更多的資料。

  • 通道是共享的,同一時刻只能有一個計算機在傳送訊息。如果每次都傳輸完整的資料,那麼其他計算機會等待很久才能響應,這對於一些實時通訊任務來說是個噩夢。

乙太網定義其基本的通訊單位是資料幀 frame,其最基本的格式如下:

為了在效率與公平上達到一個平衡,乙太網定義每個資料幀的最大為 1518。除去固定 14 位元組的頭部與 4 位元組的尾部,資料負載的長度最多為 1500 位元組。這個 1500 就是我們常說的乙太網的最大傳輸單元 —— MTU

[lhop@localhost ~]$ netstat -i
Kernel Interface table
Iface       MTU Met    RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0       1500   0   947894      0      0      0   597731      0      0      0 BMRU
lo        65536   0        0      0      0      0        0      0      0      0 LRU

MTU 是由底層網路的特性所決定的,如果兩臺主機之間的通訊要通過多個網路,那麼每個網路的鏈路層就可能有不同的 MTU。通訊路徑中的最小 MTU,決定了整個通訊鏈路的 MTU 上限,它被稱作 路徑MTU

IP 分片

IP包頭中用2個位元組描述 IP 報文總長度,因此IP資料包的最大長度是64K位元組(65535)。如果IP層有一個數據報要傳,而且資料的長度比鏈路層的MTU還大,那麼IP層就需要進行分片fragmentation,把資料報分成若干片並保證每一片都小於MTU。

IP層接收到一份要傳送的IP資料報時,它要判斷向本地哪個介面傳送資料(選路),並查詢該介面獲得其MTU。IP把MTU與資料報長度進行比較,如果需要則進行分片。

資料分片 可以發生在原始傳送端主機上,也可以發生在中間路由器上。資料重組 由目的端的IP層來完成,其目的是使分片和重新組裝過程對傳輸層 (TCP和UDP) 是透明的

當IP資料報被分片後,每一片都成為一個分組,具有自己的IP首部,並在選擇路由時與其他分組獨立。這樣,當資料報的這些片到達目的端時有可能會失序,但是在IP首部中有足夠的資訊讓接收端能正確組裝這些資料報片。

儘管IP分片過程看起來是透明的,但有一點讓人不想使用它:即使只丟失一片資料也要重傳整個資料報。其原因如下:

如果對資料報分片的是中間路由器,而不是起始端系統,那麼起始端系統就無法知道資料報是如何被分片的,無法只重傳資料報中的一個數據報片。

傳輸層分片

當在乙太網上傳輸資料時,TCP 協議在建立連線時,會根據乙太網的 MTU 與 IP 層的頭部長度來協商一個最大報文段長度 MSS = MTU - IP頭部長度 = 1472。

  • 傳送端向 IP 層傳送報文前,會按照 MSS 分割成多個報文段,然後逐個向 IP 層傳送。
  • 接收端從 IP 層接收到報文後,會將分片進行重組。

整個過程在傳輸層完成,避免了 IP 層的分片處理。

當使用 UDP 協議時,使用者傳送的報文會被原封不動的傳送給 IP 層。


UDP包頭內有總長度欄位,同樣為兩個位元組,因此UDP資料包的總長度被限制為 65535 從而保證這樣恰好可以放進一個IP包內,簡化了 UDP/IP 的實現。

當 IP 層需要傳送大於 MTU 的資料報時,IP 層會對資料進行分片—重組處理。如果 UDP 資料報較大,並且網路狀況較差的話,頻繁的重傳會造成傳輸效率的低下。

因此,使用 UDP 進行資料傳輸時,傳送端最好將 UDP 資料報的大小控制在 MSS 以內。

到達確認與超時重傳

前面說過,TCP 提供一種面向連線的、可靠的位元組流服務。而 IP 層本身不存在連線的概念,因此TCP 需要在程序間維護一個傳輸位元組流的連線。此外,為了保證訊息不會產生亂序,TCP 會為在連線中傳輸的每個位元組分配一個唯一的序列號,用於保證位元組直接的順序不會被打亂。

三次握手

建立連線是由 Client 向 Server 發起的:

  1. Client 會給 Server 傳送一個 SYN
  2. Server 每收到一個新的 SYN 包都會建立一個半連線,然後向 Client 傳送 SYNACK
  3. Client 在收到 Server 的 SYN/ACK 包後會發出 ACK,Server 收到該 ACK 後,三次握手就完成併產生了一個 TCP 全連線

在建立連線時,雙方會在 SYN 報文中協商一個初始序號 ISN

  • 傳送端每次傳送新資料時,會遞增這個序號seq,表示該報文段首位元組的位元組流編號。
  • 接受方接收到報文後,會返回一個確認序號ack,表明這個序號之前的資料已經收到。

SYN 報文的特殊之處在於:

雖然其不附帶有位元組資料,但是其本身需要佔用一個位元組序號,因此 ACK 包中返回的確認序號需要在 ISN 的基礎上遞增一個位元組。

四次揮手

斷開連線可以由 Client 或 Server 任意一端發起,也可以雙方同時發起:

  • 主動關閉:主動向對端傳送 FIN 包關閉連結,然後會接收 ACK
  • 被動關閉:接收到對端的 FIN 包後再發送 FIN 包,也會向對端回 ACK

主動關閉方維持 TIME_WAIT 狀態的意義:

最後傳送的這個 ACK 包可能會被丟棄掉或者有延遲,這樣對端就會再次傳送 FIN 包。如果不維持 TIME_WAIT 這個狀態,那麼再次收到對端的 FIN 包後,本端就會回一個 Reset 包,這可能會產生一些異常

超時重傳

前面提到,TCP 資料報可能會在傳輸過程中丟失:

  • 傳送方傳送的分組丟失
  • 接收方響應確認的分組丟失

上面兩種丟失情況,對於傳送方來說結果是相同的:接收不到確認分組。因此 TCP 引入了超時重傳機制來解決報文丟失的問題:

傳送端每傳送一個報文段,TCP會為其保留一個副本並設定一個計時器並等待確認資訊。如果計時器超時,而傳送的報文段中的資料仍未得到確認,則重傳這一報文段,直到傳送成功為止。

影響超時重傳機制協議效率的一個關鍵引數是重傳超時時間 RTO:

  • RTO 過大將會使傳送端經過較長時間的等待才能發現報文段丟失,降低了連線資料傳輸的吞吐量
  • RTO 過小可能將一些延遲大的報文段誤認為是丟失,造成不必要的重傳,浪費了網路資源

TCP協議採用自適應演算法記錄資料包的往返時延 RRT,並根據往返時延設定 RTO 的取值。一般來說,RTO 的取值會略大於 RTT 以保證資料包的正常傳輸。

擁塞視窗與接收視窗

擁塞視窗

在實際的應用中,一個 TCP 連線傳輸資料時不可能獨佔網路,因此網路的可用頻寬是動態變化的。為了最大化的利用網路頻寬,TCP 實現了一個動態調整傳輸速率的擁塞控制機制:

  • 網路空閒時提升傳輸速度,提高資料傳輸效率
  • 網路擁塞時降低傳輸速度,避免丟包觸發不必要的超時重傳

該機制的核心是傳送端的擁塞視窗

在傳送端設定一個大小為 cwnd 傳送視窗結構,cwnd 根據網路的擁塞情況(TCP 重傳率)動態變化,傳送端只能傳送小於或等於擁塞視窗大小的資料。

擁塞控制是 TCP 協議的核心,並且對傳輸效能有著至關重要的影響,大致可將其分為 4 個階段:

  • 慢啟動
TCP 連線建立好後,傳送方就進入慢速啟動階段。初始時 cwnd 為 1,然後逐漸地增大發包數量。這個階段每經過一個 RTT,發包數量就會翻倍,直到 cwnd 增大到一個閾值 ssthreshold(該階段的 cwnd 以指數形式增長)
  • 擁塞避免
當 cwnd 增大到 ssthreshold 後 TCP 就進入了擁塞避免階段。在這個階段 cwnd 不再成倍增加,而是一個 RTT 增加 1,即緩慢地增加 cwnd,以防止網路出現擁塞(該階段的 cwnd 線性增長)
  • 快速重傳 / 超時重傳
隨著傳輸速率的不斷上升,丟包是難以避免的,針對不同的丟包場景,TCP 引入了兩種重傳策略:
  • 超時重傳
    傳送端超過 RTO 後仍然收不到 ack 響應,會據此判斷此時網路已經出現了擁塞:

    1. ssthreshold = cwnd / 2
    2. cwnd = 1
    3. 進入慢啟動階段
  • 快速重傳
    傳送端連續收到 3 個重複的 ack 序號後,會據此判斷資料包出現了丟失,但此時網路未出現擁塞情況(擁塞視窗不必恢復到初始值):

    1. cwnd = cwnd / 2
    2. ssthreshold = cwnd
    3. 進入快速恢復階段
  • 快速恢復
快速重傳階段檢測到報文丟失後,會進入快速恢復階段,立即重傳丟失的資料段而無需等待 RTO 超時:
  • cwnd = cwnd + 3 * MSS
  • 重傳資料
  • 再每收到一個重複ACK,cwnd = cwnd + 1
  • 直到收到不重複ACK,設定cwnd = ssthreshold,進入擁塞避免階段

接收視窗

除了網路狀況外,傳送方還需要知道接收方的處理能力。如果接收方的處理能力差,那麼傳送方就必須要減緩它的發包速度。接收方的處理能力是通過接收視窗 rwnd 來表示的:

  • 接收方在收到資料包後,會給傳送方回一個 ack,並後把自己的 rwnd 大小寫入到 TCP 頭部的 win 這個欄位,這樣傳送方就能根據這個欄位來知道接收方的 rwnd
  • 傳送方在傳送下一個 TCP 資料報的時候,會先對比傳送方的 cwnd 和接收方的 rwnd,得出這二者之間的較小值,然後控制傳送的資料量不能超過這個較小值



參考資料: