TCP協議詳解

前言

小到基於應用層作網絡開發,大到生活中無處不在的網絡。咱們在享受這個便利的時候,沒有人會關心它如此牢固的底層基石是如何搭建的。而這些基石中很重要的一環就是tcp協議。翻看一下「三次握手」和「四次揮手」,本覺得這就是tcp了,其實否則。它僅僅解決了鏈接和關閉的問題,傳輸的問題纔是tcp協議更重要,更難,更復雜的問題。回頭看tcp協議的原理,會發現它爲了承諾上層數據傳輸的「可靠」,不知要應對多少網絡中複雜多變的狀況。簡單直白列舉一下:html

  • 怎麼保證數據都是可靠呢?---鏈接確認!關閉確認!收到數據確認!各類確認!!
  • 由於網絡或其餘緣由,對方收不到數據怎麼辦?--超時重試
  • 網絡狀況變幻無窮,超時時間怎麼肯定?--根據RTT動態計算
  • 反反覆覆,不厭其煩的重試,致使網絡擁塞怎麼辦?---慢啓動,擁塞避免,快速重傳,快速恢復
  • 發送速度和接收速度不匹配怎麼辦?--滑動窗口
  • 滑動窗口滑的過程當中,他一直告訴我處理不過來了,不讓傳數據了怎麼辦?--ZWP
  • 滑動窗口滑的過程當中,他處理得慢,就理所固然的每次讓我發不多的數據,致使網絡利用率很低怎麼辦?---Nagle

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

梳理完全部內容,大概能夠知道:算法

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

tcp鏈接的「三次握手」和關閉的「四次揮手」流程是怎麼樣的?

tcp鏈接和關閉過程當中,狀態是如何變化的?

tcp頭部有哪些字段,分別用來作什麼的?

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

超時重傳的機制是什麼?

如何避免傳輸擁塞?

一. 概述

1. tcp鏈接的特色

  • 提供面向鏈接的,可靠的字節流服務
  • 爲上層應用層提供服務,不關心具體傳輸的內容是什麼,也不知道是二進制流,仍是ascii字符。

2. tcp的可靠性如何保證

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

3. tcp的首部格式

3.1 宏觀位置

  • 從應用層->傳輸層->網絡層->鏈路層,每通過一次都會在報文中增長相應的首部。參考以前的文章http協議
  • TCP數據被封裝在IP數據報中

3.2 首部格式

  • tcp首部數據一般包含20個字節(不包括任選字段)
  • 第1-2兩個字節:源端口號
  • 第3-4兩個字節:目的端口號

    源端口號+ip首部中的源ip地址+目的端口號+ip首部中的目的ip地址,惟一的肯定了一個tcp鏈接。對應編碼級別的socket。shell

  • 第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選項格式

  • 每一個選項開始是1字節kind字段,說明選項的類型
  • kind爲0和1的選項,只佔一個字節
  • 其餘kind後有一字節len,表示該選項總長度(包括kind和len)
  • kind爲11,12,13表示tcp事務

3.5 MSS 最長報文大小

  • 最多見的可選字段
  • MSS只能出如今SYN時傳過來(第一次握手和第二次握手時)
  • 指明本端能接收的最大長度的報文段
  • 創建鏈接時,雙方都要發送MSS
  • 若是不發送,默認爲536字節

二. 鏈接的創建與釋放

1. 鏈接創建的「三次握手」

1.1 三次握手流程

  • 客戶端發送SYN,代表要向服務器創建鏈接。同時帶上序列號ISN
  • 服務器返回ACK(序號爲客戶端序列號+1)做爲確認。同時發送SYN做爲應答(SYN的序列號爲服務端惟一的序號)
  • 客戶端發送ACK確認收到回覆(序列號爲服務端序列號+1)

1.2 爲何是三次握手

  • tcp鏈接是全雙工的,數據在兩個方向上能同時傳遞。
  • 因此要確保雙方,同時能發數據和收數據
  • 第一次握手:證實了發送方能發數據
  • 第二次握手:ack確保了接收方能收數據,syn確保了接收方能發數據
  • 第三次握手:確保了發送方能收數據
  • 其實是四個維度的信息交換,不過中間兩步合併爲一次握手了。
  • 四次握手浪費,兩次握手不能保證「雙方同時具有收發功能」

2. 鏈接關閉的「四次揮手」

2.1 爲何是四次揮手

  • 由於tcp鏈接是全雙工的,數據在兩個方向上能同時傳遞。
  • 同時tcp支持半關閉(發送一方結束髮送還能接收數據的功能)。
  • 所以每一個方向都要單獨關閉,且收到關係通知須要發送確認回覆

2.2 爲何要支持半關閉

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

2.3 四次握手流程

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

3. 鏈接和關閉對應的狀態

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. 同時打開

  • 兩個應用程序同時執行主動打開,稱爲「同時打開「
  • 這種狀況極少發生
  • 兩端同時發送SYN,同時進入SYN_SENT狀態
  • 打開一條鏈接而不是兩條
  • 要進行四次報文交換過程,「四次握手」

6. 同時關閉

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

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緩衝區的數據結構

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

3. 滑動窗口示意圖

3.1 初始時示意圖

  • 黑框表示滑動窗口
  • #1表示收到ack確認的數據
  • #2表示還沒收到ack的數據
  • #3表示在窗口中尚未發出的(接收方還有空間)
  • #4窗口之外的數據(接收方沒空間)

3.2 滑動過程示意圖

  • 收到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,就重傳。不用等待超時
  • 圖中發生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上沒有數據傳輸時,服務器如何檢測到客戶端是否還存活

參考

相關文章
相關標籤/搜索