TCP協議詳解

kinnylee發表於2018-09-28

前言

小到基於應用層做網路開發,大到生活中無處不在的網路。我們在享受這個便利的時候,沒有人會關心它如此牢固的底層基石是如何搭建的。而這些基石中很重要的一環就是tcp協議。翻看一下“三次握手”和“四次揮手”,本以為這就是tcp了,其實不然。它僅僅解決了連線和關閉的問題,傳輸的問題才是tcp協議更重要,更難,更復雜的問題。回頭看tcp協議的原理,會發現它為了承諾上層資料傳輸的“可靠”,不知要應對多少網路中複雜多變的情況。簡單直白列舉一下:

  • 怎麼保證資料都是可靠呢?---連線確認!關閉確認!收到資料確認!各種確認!!
  • 因為網路或其他原因,對方收不到資料怎麼辦?--超時重試
  • 網路情況千變萬化,超時時間怎麼確定?--根據RTT動態計算
  • 反反覆覆,不厭其煩的重試,導致網路擁塞怎麼辦?---慢啟動,擁塞避免,快速重傳,快速恢復
  • 傳送速度和接收速度不匹配怎麼辦?--滑動視窗
  • 滑動視窗滑的過程中,他一直告訴我處理不過來了,不讓傳資料了怎麼辦?--ZWP
  • 滑動視窗滑的過程中,他處理得慢,就理所當然的每次讓我發很少的資料,導致網路利用率很低怎麼辦?---Nagle

其中任何一個小環節,都凝聚了無數的演算法,我們沒有能力理解各個演算法的實現,但是需要了解下tcp實現者的思路歷程。

梳理完所有內容,大概可以知道:

tcp提供哪些機制保證了資料傳輸的可靠性?

tcp連線的“三次握手”和關閉的“四次揮手”流程是怎麼樣的?

tcp連線和關閉過程中,狀態是如何變化的?

tcp頭部有哪些欄位,分別用來做什麼的?

tcp的滑動視窗協議是什麼?

超時重傳的機制是什麼?

如何避免傳輸擁塞?

一. 概述

1. tcp連線的特點

  • 提供面向連線的,可靠的位元組流服務
  • 為上層應用層提供服務,不關心具體傳輸的內容是什麼,也不知道是二進位制流,還是ascii字元。

2. tcp的可靠性如何保證

  • 分塊傳送:資料被分割成最合適的資料塊(UDP的資料包長度不變)
  • 等待確認:通過定時器等待接收端傳送確認請求,收不到確認則重發
  • 確認回覆:收到確認後傳送確認回覆(不是立即傳送,通常推遲幾分之一秒)
  • 資料校驗:保持首部和資料的校驗和,檢測資料傳輸過程有無變化
  • 亂序排序:接收端能重排序資料,以正確的順序交給應用端
  • 重複丟棄:接收端能丟棄重複的資料包
  • 流量緩衝:兩端有固定大小的緩衝區(滑動視窗),防止速度不匹配丟資料

3. tcp的首部格式

3.1 巨集觀位置

TCP協議詳解

  • 從應用層->傳輸層->網路層->鏈路層,每經過一次都會在報文中增加相應的首部。參考之前的文章http協議
  • TCP資料被封裝在IP資料包中

3.2 首部格式

TCP協議詳解

  • tcp首部資料通常包含20個位元組(不包括任選欄位)
  • 第1-2兩個位元組:源埠號
  • 第3-4兩個位元組:目的埠號

    源埠號+ip首部中的源ip地址+目的埠號+ip首部中的目的ip地址,唯一的確定了一個tcp連線。對應編碼級別的socket。

  • 第5-8四個位元組:32位序號。tcp提供全雙工服務,兩端都有各自的序號。編號:解決網路包亂序的問題

    序號如何生成:不能是固定寫死的,否則斷網重連時序號重複使用會亂套。tcp基於時鐘生成一個序號,每4微秒加一,到2^32-1時又從0開始

  • 第9-12四個位元組:32位確認序列號。上次成功收到資料位元組序號加1,ack為1才有效。確認號:解決丟包的問題
  • 第13位位元組:首部長度。因為任選欄位長度可變
  • 後面6bite:保留
  • 隨後6bite:標識位。控制各種狀態
  • 第15-16兩個位元組:視窗大小。接收端期望接收的位元組數。解決流量控制的問題
  • 第17-18兩個位元組:校驗和。由傳送端計算和儲存,由接收端校驗。解決資料正確性問題
  • 第19-20兩個位元組:緊急指標

3.3 標識位說明

  • URG:為1時,表示緊急指標有效
  • ACK:確認標識,連線建立成功後,總為1。為1時確認號有效
  • PSH:接收方應儘快把這個報文交給應用層
  • RST:復位標識,重建連線
  • SYN:建立新連線時,該位為0
  • FIN:關閉連線標識

3.4 tcp選項格式

TCP協議詳解

  • 每個選項開始是1位元組kind欄位,說明選項的型別
  • kind為0和1的選項,只佔一個位元組
  • 其他kind後有一位元組len,表示該選項總長度(包括kind和len)
  • kind為11,12,13表示tcp事務

3.5 MSS 最長報文大小

  • 最常見的可選欄位
  • MSS只能出現在SYN時傳過來(第一次握手和第二次握手時)
  • 指明本端能接收的最大長度的報文段
  • 建立連線時,雙方都要傳送MSS
  • 如果不傳送,預設為536位元組

二. 連線的建立與釋放

1. 連線建立的“三次握手”

1.1 三次握手流程

TCP協議詳解

  • 客戶端傳送SYN,表明要向伺服器建立連線。同時帶上序列號ISN
  • 伺服器返回ACK(序號為客戶端序列號+1)作為確認。同時傳送SYN作為應答(SYN的序列號為服務端唯一的序號)
  • 客戶端傳送ACK確認收到回覆(序列號為服務端序列號+1)

1.2 為什麼是三次握手

  • tcp連線是全雙工的,資料在兩個方向上能同時傳遞。
  • 所以要確保雙方,同時能發資料和收資料
  • 第一次握手:證明了傳送方能發資料
  • 第二次握手:ack確保了接收方能收資料,syn確保了接收方能發資料
  • 第三次握手:確保了傳送方能收資料
  • 實際上是四個維度的資訊交換,不過中間兩步合併為一次握手了。
  • 四次握手浪費,兩次握手不能保證“雙方同時具備收發功能”

2. 連線關閉的“四次揮手”

2.1 為什麼是四次揮手

  • 因為tcp連線是全雙工的,資料在兩個方向上能同時傳遞。
  • 同時tcp支援半關閉(傳送一方結束髮送還能接收資料的功能)。
  • 因此每個方向都要單獨關閉,且收到關係通知需要傳送確認回覆

2.2 為什麼要支援半關閉

  • 客戶端需要通知服務端,它的資料已經傳輸完畢
  • 同時仍要接收來自服務端的資料
  • 使用半關閉的單連線效率要比使用兩個tcp連線更好

2.3 四次握手流程

TCP協議詳解

  • 主動關閉的一方傳送FIN,表示要單方面關閉資料的傳輸
  • 服務端收到FIN後,傳送一個ACK作為確認(序列號為收到的序列號+1)
  • 等伺服器資料傳輸完畢,也傳送一個FIN標識,表示關閉這個方向的資料傳輸
  • 客戶端回覆ACK以確認回覆

3. 連線和關閉對應的狀態

TCP協議詳解

3.1 狀態說明

  • 服務端等待客戶端連線時,處於Listen監聽狀態
  • 客戶端主動開啟請求,傳送SYN時處於SYN_SENT傳送狀態
  • 客戶端收到syn和ack,並回復ack時,處與Established狀態等待傳送報文
  • 服務端收到ack確認後,也處於Established狀態等待傳送報文
  • 客戶端傳送fin後,處於fin_wait_1狀態
  • 服務端收到fin併傳送ack時,處於close_wait狀態
  • 客戶端收到ack確認後,處於fin_wait_2狀態
  • 服務端傳送fin後,處於last_ack狀態
  • 客戶端收到fin後傳送ack,處於time_wait狀態
  • 服務端收到ack後,處於closed狀態

3.2 time_wait狀態

  • 也稱為2MSL等待狀態,MSL=Maximum Segment LifetIme,報文段最大生存時間,根據不同的tcp實現自行設定。常用值為30s,1min,2min。linux一般為30s。
  • 主動關閉的一方傳送最後一個ack所處的狀態
  • 這個狀態必須維持2MSL等待時間

3.2.1 為什麼需要這麼做?

  • 設想一個場景,最後這個ack丟失了,接收方沒有收到
  • 這時候接收方會重新傳送fin給傳送方
  • 這個等待時間就是為了防止這種情況發生,讓傳送方重新傳送ack
  • 總結:預留足夠的時間給接收端收ack。同時保證,這個連線不會和後續的連線亂套(有些路由器會快取資料包)

3.2.2 這麼做的後果?

  • 在這2MSL等待時間內,該連線(socket,ip+port)將不能被使用
  • 很多時候linux上報too many open files,說埠不夠用了,就需要檢查一些程式碼裡面是不是建立大量的socket連線,而這些socket連線並不是關閉後就立馬釋放的
  • 客戶端連線伺服器的時候,一般不指定客戶端的埠。因為客戶端關閉然後立馬啟動,按照理論來說是會提示埠被佔用。同樣的道理,主動關閉伺服器,2MSL時間內立馬啟動是會報埠被佔用的錯誤
  • 多併發的短連線情況下,會出現大量的Time_wait狀態。這兩個引數可以解決問題,但是它違背了tcp協議,是有風險的。引數為:tcp_tw_reuse和tcp_tw_recycle
  • 如果是服務端開發,可設定keep-alive,讓客戶端主動關閉連線解決這個問題

4. 復位報文段

一個報文段從源地址發往目的地址,只要出現錯誤,都會發出復位的報文段,首部欄位的RST是用於“復位”的。這些錯誤包括以下情況

  • 埠沒有在監聽
  • 異常中止:通過傳送RST而不是fin來中止連線

5. 同時開啟

TCP協議詳解

  • 兩個應用程式同時執行主動開啟,稱為“同時開啟“
  • 這種情況極少發生
  • 兩端同時傳送SYN,同時進入SYN_SENT狀態
  • 開啟一條連線而不是兩條
  • 要進行四次報文交換過程,“四次握手”

6. 同時關閉

TCP協議詳解

  • 雙方同時執行主動關閉
  • 進行四次報文交換
  • 狀態和正常關閉不一樣

7. 伺服器對於併發請求的處理

  • 正等待連線的一端有一個固定長度的佇列(長度叫做“積壓值”,大多數情況長度為5)
  • 該佇列中的連線為:已經完成了三次握手,但還沒有被應用層接收(應用層需要等待最後一個ack收到後才知道這個連線)
  • 應用層接收請求的連線,將從該佇列中移除
  • 當新的請求到來時,先判斷佇列情況來決定是否接收這個連線
  • 積壓值的含義:tcp監聽的端點已經被tcp接收,但是等待應用層接收的最大值。與系統允許的最大連線數,伺服器接收的最大併發數無關

三. 資料的傳輸

1. tcp傳輸的資料分類

  • 成塊資料傳輸:量大,報文段常常滿
  • 互動資料傳輸:量小,報文段為微小分組,大量微小分組,在廣域網傳輸會增加擁堵的出現
  • tcp處理的資料包括兩類,有不同的特點,需要不同的傳輸技術

2. 互動資料的傳輸技術

2.1 經受時延的確認

  • 概念:tcp收到資料時,並不立馬傳送ack確認,而是稍後傳送
  • 目的:將ack與需要沿該方向傳送的資料一起傳送,以減少開銷
  • 特點:接收方不必確認每一個收到的分組,ACk是累計的,它表示接收方已經正確收到了一直到確認序號-1的所有位元組
  • 延時時間:絕大多數為200ms。不能超過500ms

2.2 Nagle演算法

  • 解決什麼問題:微小分組導致在廣域網出現的擁堵問題
  • 核心:減少了通過廣域網傳輸的小分組數目
  • 原理:要求一個tcp連線上最多隻能有一個未被確認的未完成的分組,該分組的確認到達之前,不能傳送其他分組。tcp收集這些分組,確認到來之前以一個分組的形式發出去
  • 優點:自適應。確認到達的快,資料傳送越快。確認慢,傳送更少的組。
  • 使用注意:區域網很少使用該演算法。且有些特殊場景需要禁用該演算法

3. 成塊資料的傳輸

  • 主要使用滑動視窗協議

四. 滑動視窗協議

1. 概述

  • 解決了什麼問題:傳送方和接收方速率不匹配時,保證可靠傳輸和包亂序的問題
  • 機制:接收方根據目前緩衝區大小,通知傳送方目前能接收的最大值。傳送方根據接收方的處理能力來傳送資料。通過這種協調機制,防止接收端處理不過來。
  • 視窗大小:接收方發給傳送端的這個值稱為視窗大小

2. tcp緩衝區的資料結構

TCP協議詳解

  • 接收端:
    • LastByteRead: 緩衝區讀取到的位置
    • NextByteExpected:收到的連續包的最後一個位置
    • LastByteRcvd:收到的包的最後一個位置
    • 中間空白區:資料沒有到達
  • 傳送端:
    • LastByteAcked: 被接收端ack的位置,表示成功傳送確認
    • LastByteSent:發出去了,還沒有收到成功確認的Ack
    • LastByteWritten:上層應用正在寫的地方

3. 滑動視窗示意圖

3.1 初始時示意圖

TCP協議詳解

  • 黑框表示滑動視窗
  • #1表示收到ack確認的資料
  • #2表示還沒收到ack的資料
  • #3表示在視窗中還沒有發出的(接收方還有空間)
  • #4視窗以外的資料(接收方沒空間)

3.2 滑動過程示意圖

TCP協議詳解

  • 收到36的ack,併發出46-51的位元組

4. 擁塞視窗

  • 解決什麼問題:傳送方傳送速度過快,導致中轉路由器擁堵的問題
  • 機制:傳送方增加一個擁塞視窗(cwnd),每次受到ack,視窗值加1。傳送時,取擁塞視窗和接收方發來的視窗大小取最小值傳送
  • 起到傳送方流量控制的作用

5. 滑動視窗會引發的問題

5.1 零視窗

  • 如何發生: 接收端處理速度慢,傳送端傳送速度快。視窗大小慢慢被調為0
  • 如何解決:ZWP技術。傳送zwp包給接收方,讓接收方ack他的視窗大小。

5.2 糊塗視窗綜合徵

  • 如何發生:接收方太忙,取不完資料,導致傳送方越來越小。最後只讓傳送方傳幾位元組的資料。
  • 缺點:資料比tcp和ip頭小太多,網路利用率太低。
  • 如何解決:避免對小的視窗大小做響應。
    • 傳送端:前面說到的Nagle演算法。
    • 接收端:視窗大小小於某個值,直接ack(0),阻止傳送資料。視窗變大後再發。

五. 超時與重傳

1. 概述

  • tcp提供可靠的運輸層,使用的方法是確認機制。
  • 但是資料和確認都有可能丟失
  • tcp通過在傳送時設定定時器解決這種問題
  • 定時器時間到了還沒收到確認,就重傳該資料

2. tcp管理的定時器型別

  • 重傳定時器:等待收到確認
  • 堅持定時器:使視窗大小資訊保持不斷流動
  • 保活定時器:檢測空閒連線崩潰或重啟
  • 2MSL定時器:檢測time_wait狀態

3. 超時重傳機制

3.1 背景

  • 接收端給傳送端的Ack確認只會確認最後一個連續的包
  • 比如傳送1,2,3,4,5共五份資料,接收端收到1,2,於是回ack3,然後收到4(還沒收到3),此時tcp不會跳過3直接確認4,否則傳送端以為3也收到了。這時你能想到的方法是什麼呢?tcp又是怎麼處理的呢?

3.1 被動等待的超時重傳策略

  • 直觀的方法是:接收方不做任何處理,等待傳送方超時,然後重傳。
    • 缺點:傳送端不知道該重發3,還是重發3,4,5
  • 如果傳送方如果只傳送3:節省寬度,但是慢
  • 如果傳送方如果傳送3,4,5:快,但是浪費寬頻
  • 總之,都在被動等待超時,超時可能很長。所以tcp不採用此方法

3.2 主動的快速重傳機制

3.2.1 概述

  • 名稱為:Fast Retransmit
  • 不以實際驅動,而以資料驅動重傳

3.2.2 實現原理

  • 如果包沒有送達,就一直ack最後那個可能被丟的包
  • 傳送方連續收到3相同的ack,就重傳。不用等待超時
    TCP協議詳解
  • 圖中發生1,2,3,4,5資料
  • 資料1到達,發生ack2
  • 資料2因為某些原因沒有送到
  • 後續收到3的時候,接收端並不是ack4,也不是等待。而是主動ack2
  • 收到4,5同理,一直主動ack2
  • 客戶端收到三次ack2,就重傳2
  • 2收到後,結合之前收到的3,4,5,直接ack6

3.2.3 快速重傳的利弊

  • 解決了被動等待timeout的問題
  • 無法解決重傳之前的一個,還是所有的問題。
  • 上面的例子中是重傳2,還是重傳2,3,4,5。因為並不清楚ack2是誰傳回來的

3.3 SACK方法

3.3.1 概述

  • 為了解決快速重傳的缺點,一種更好的SACK重傳策略被提出
  • 基於快速重傳,同時在tcp頭裡加了一個SACK的東西
  • 解決了什麼問題:客戶端應該傳送哪些超時包的問題

3.3.2 實現原理

  • SACK記錄一個數值範圍,表示哪些資料收到了
  • linux2.4後預設開啟該功能,之前版本需要配置tcp-sack引數
  • SACK只是一種輔助的方式,傳送方不能完全依賴SACK。主要還是依賴ACK和timout

3.3.3 Duplicate SACK(D-SACK)

  • 使用SACK標識的範圍,還可以知道告知傳送方,有哪些資料被重複接收了
  • 可以讓傳送方知道:是發出去的包丟了,還是回來的ack包丟了

4. 超時時間的確定

4.1 背景

  • 路由器和網路流量均會變化
  • 所以超時時間肯定不能設定為一個固定值
  • 超時長:重發慢,效率低,效能差
  • 超時短:並沒有丟就重發,導致網路擁塞,導致更多超時和更多重發
  • tcp會追蹤這些變化,並相應的動態改變超時時間(RTO)

4.2 如何動態改變

  • 每次重傳的時間間隔為上次的一倍,直到最大間隔為64s,稱為“指數退避”
  • 首次重傳到最後放棄重傳的時間間隔一般為9min
  • 依賴以往的往返時間計算(RTT)動態的計算

4.3 往返時間(RTT)的計算方法

  • 並不是簡單的ack時間和傳送時間的差值。因為有重傳,網路阻塞等各種變化的因素。
  • 而是通過取樣多次數值,然後做估算
  • tcp使用的方法有:
    • 被平滑的RTT估計器
    • 被平滑的均值偏差估計器

4.4. 重傳時間的具體計算

  • 計算往返時間(RTT),儲存測量結果
  • 通過測量結果維護一個被平滑的RTT估計器和被平滑的均值偏差估計器
  • 根據這兩個估計器計算下一次重傳時間

5. 超時重傳引發的問題-擁塞

5.1 為什麼重傳會引發擁塞

  • 當網路延遲突然增加時,tcp會重傳資料
  • 但是過多的重傳會導致網路負擔加重,從而導致更大的延時和丟包,進入惡性迴圈
  • 也就是tcp的擁塞問題

5.2 解決擁塞-擁塞控制的演算法

  • 慢啟動:降低分組進入網路的傳輸速率
  • 擁塞避免:處理丟失分組的演算法
  • 快速重傳
  • 快速恢復

六. 其他定時器

1. 堅持定時器

1.1 堅持定時器存在的意義

  • 當視窗大小為0時,接收方會傳送一個沒有資料,只有視窗大小的ack
  • 但是,如果這個ack丟失了會出現什麼問題?雙方可能因為等待而中止連線
  • 堅持定時器週期性的向接收方查詢視窗是否被增大。這些發出的報文段稱為視窗探查

1.2 堅持定時器啟動時機

  • 傳送方被通告接收方視窗大小為0時

1.3 與超時重傳的相同和不同

  • 相同:同樣的重傳時間間隔
  • 不同:視窗探查從不放棄傳送,直到視窗被開啟或者程式被關閉。而超時重傳到一定時間就放棄傳送

2. 保活定時器

2.1 保活定時器存在的意義

  • 當tcp上沒有資料傳輸時,伺服器如何檢測到客戶端是否還存活

參考

相關文章