TCP知識

一、TCP 簡介

第一部分先為大家介紹一下 TCP 的主要概念,并講解一下 TCP 的三個重要特性。

  1. 面向連接;
  2. 基于字節(jié)流;
  3. 可靠性。

關(guān)于網(wǎng)絡(luò)分層的概念實在是老生常談了,下圖就是兩種經(jīng)典的分層模型,可以看到 TCP 在網(wǎng)絡(luò)分層中的位置。

網(wǎng)絡(luò)分層模型

本文重點對 TCP 進行介紹,從圖中可以看到 TCP 位于傳輸層,而且構(gòu)建于網(wǎng)絡(luò)層的 IP 協(xié)議之上,對于 TCP 最常見的介紹就是 “TCP 是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議”,那這三個形容詞究竟是什么意思呢?

1.1 面向連接

面向連接意味著兩個使用 TCP 的應(yīng)用 (通常是一個客戶端和一個服務(wù)器) 在彼此交換數(shù)據(jù)之前必須先建立一個 TCP 連接。這一過程與打電話很相似,先撥號響鈴,等對方應(yīng)答后再說明是誰。詳細的三次握手、四次揮手過程將在第二部分 —— 連接管理部分進行介紹。

1.2 基于字節(jié)流

TCP 連接雙方的數(shù)據(jù)交換格式是以字節(jié) (byte,1byte = 8 bit)構(gòu)成的有序但無結(jié)構(gòu)的字節(jié)流。TCP 不在字節(jié)流中插入記錄標(biāo)識符,這被稱為字節(jié)流服務(wù)(byte stream service)。

如果一方的應(yīng)用程序先傳 10 字節(jié),又傳 20 字節(jié),再傳 50 字節(jié) ,連接的另一方將無法了解發(fā)方每次發(fā)送了多少字節(jié)。收方可以分 4 次接收這 80 個字節(jié),每次接收 20 字節(jié)。一端將字節(jié)流放到 TCP 連接上,同樣的字節(jié)流將出現(xiàn)在 TCP 連接的另一端。另外,TCP 對字節(jié)流的內(nèi)容不作任何解釋,TCP 無法知道傳輸?shù)臄?shù)據(jù)字節(jié)流是二進制數(shù)據(jù),還是 ASCI I 字符。

如果覺得上面這段話比較抽象的話,可以拿 TCP 的字節(jié)流和 UDP 的報文 (message) 進行比較(UDP:User Datagram Protocol,用戶數(shù)據(jù)報協(xié)議,和 TCP 同為傳輸層的協(xié)議,后面會提供兩者的全面對比)。TCP 的字節(jié)流類似于自來水,連接雙方都有緩沖區(qū),可以類比成蓄水池,發(fā)送方的發(fā)送頻率和每次的發(fā)送量沒有固定要求,接收方也可以自由決定自己的接收頻率和每次的接收量,只要把所有的數(shù)據(jù)接收完畢即可。而 UDP 的報文則類似于瓶裝水 (比如農(nóng)夫山泉),發(fā)送方發(fā)送一瓶,接收方就要相應(yīng)地接收一瓶。

下圖描述了 TCP 連接中數(shù)據(jù)的傳輸過程以及 TCP 在整個過程中所扮演的角色。

TCP 在網(wǎng)絡(luò)數(shù)據(jù)傳輸中的位置和角色

按照圖中的流程,比如我們在瀏覽B站,在 TCP 連接建立之后,客戶端的應(yīng)用層協(xié)議可以向 TCP 發(fā)送無特殊格式的字節(jié)流,TCP 會將這些字節(jié)打包成報文段(segment),報文段大小視情況而定,這些報文段會被網(wǎng)絡(luò)層的 IP 封裝成 IP 數(shù)據(jù)報(IP Datagram),然后經(jīng)過網(wǎng)絡(luò)傳輸給服務(wù)器,而接下來服務(wù)器的操作相當(dāng)于客戶端的逆操作,先從 IP 數(shù)據(jù)報中拆分出 TCP 報文段,再把 TCP 報文段還原成字節(jié)流并發(fā)送給上層的應(yīng)用層協(xié)議。服務(wù)器向客戶端發(fā)送數(shù)據(jù)的流程也是一樣的,發(fā)送方和接收方的角色互換即可。

報文段簡介

上面多次提到了報文段的概念,其結(jié)構(gòu)非常重要,后面的連接過程和擁塞控制等內(nèi)容也要用到相關(guān)概念,先在這里介紹一下。


TCP 報文段結(jié)構(gòu)

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

  • 端口號 (Source Port and Destination Port):每個 TCP 報文段都包含源端和目的端的端口號,用于尋找發(fā)送端和接收端應(yīng)用進程。這兩個值加上 IP 首部中的源端 IP 地址和目的端 IP 地址就可以確定一個唯一的 TCP 連接。
  • 序號 (Sequence Number):這個字段的主要作用是用于將失序的數(shù)據(jù)重新排列。TCP 會隱式地對字節(jié)流中的每個字節(jié)進行編號,而 TCP 報文段的序號被設(shè)置為其數(shù)據(jù)部分的第一個字節(jié)的編號。序號是 32 bit 的無符號數(shù),取值范圍是0到 232 - 1。
  • 確認序號 (Acknowledgment Number):接收方在接受到數(shù)據(jù)后,會回復(fù)確認報文,其中包含確認序號,作用就是告訴發(fā)送方自己接收到了哪些數(shù)據(jù),下一次數(shù)據(jù)從哪里開始發(fā),因此,確認序號應(yīng)當(dāng)是上次已成功收到數(shù)據(jù)字節(jié)序號加 1。只有 ACK 標(biāo)志為 1 時確認序號字段才有效。
  • 首部長度 (Header Length):首部中的選項部分的長度是可變的,因此首部的長度也是可變的,所以需要這個字段來明確表示首部的長度,這個字段占 4 bit,4 位的二進制數(shù)最大可以表示 15,而首部長度是以 4 個字節(jié)為一個單位的,因此首部最大長度是 15 * 4 = 60 字節(jié)。
  • 保留字段 (Reserved):占 6 位,未來可能有具體用途,目前默認值為0.
  • 控制位 (Control Bits):在三次握手和四次揮手中會經(jīng)??吹?SYN、ACK 和 FIN 的身影,一共有 6 個標(biāo)志位,它們表示的意義如下:

URG (Urgent Bit):值為 1 時,緊急指針生效
ACK (Acknowledgment Bit):值為 1 時,確認序號生效
PSH (Push Bit):接收方應(yīng)盡快將這個報文段交給應(yīng)用層
RST (Reset Bit):發(fā)送端遇到問題,想要重建連接
SYN (Synchronize Bit):同步序號,用于發(fā)起一個連接
FIN (Finish Bit):發(fā)送端要求關(guān)閉連接

  • 窗口大小 (Window): TCP的流量控制由連接的每一端通過聲明的窗口大小來提供。窗口大小為字節(jié)數(shù),起始于確認序號字段指明的值,這個值是接收端正期望接收的字節(jié)。窗口大小是一個 16 bit 字段,單位是字節(jié), 因而窗口大小最大為 65535 字節(jié)。
  • 檢驗和 (Checksum):功能類似于數(shù)字簽名,用于驗證數(shù)據(jù)完整性,也就是確保數(shù)據(jù)未被修改。檢驗和覆蓋了整個 TCP 報文段,包括 TCP 首部和 TCP 數(shù)據(jù),發(fā)送端根據(jù)特定算法對整個報文段計算出一個檢驗和,接收端會進行計算并驗證。
  • 緊急指針 (Urgent Pointer):當(dāng) URG 控制位值為 1 時,此字段生效,緊急指針是一個正的偏移量,和序號字段中的值相加表示緊急數(shù)據(jù)最后一個字節(jié)的序號。 TCP 的緊急方式是發(fā)送端向另一端發(fā)送緊急數(shù)據(jù)的一種方式。
  • 選項 (Options):這一部分是可選字段,也就是非必須字段,最常見的可選字段是“最長報文大小 (MSS,Maximum Segment Size)”。
  • 有效數(shù)據(jù)部分 (Data):這部分也不是必須的,比如在建立和關(guān)閉 TCP 連接的階段,雙方交換的報文段就只包含 TCP 首部。

1.3 可靠性

我們都知道 TCP 是具有可靠性的通信協(xié)議,它主要通過以下方式確保可靠性,這里先了解一下可靠性的原理,其中細節(jié)部分后文會講:

  • 檢驗和:發(fā)送端按照特定算法計算出 TCP 報文段的檢驗和并存儲在 TCP 首部中的對應(yīng)字段上,接收端在接收時會以同樣的方式計算校驗和,如果不一致,說明報文段出現(xiàn)錯誤,會將其丟棄。
  • 序號與確認序號:對亂序的數(shù)據(jù)進行排序后發(fā)給應(yīng)用層,并丟棄重復(fù)的數(shù)據(jù)。
  • 超時重傳機制:當(dāng) TCP 發(fā)出一個報文段后,它會啟動一個定時器,等待目的端確認收到這個報文段。如果不能及時收到一個確認,將重發(fā)這個報文段,后面會細講這個機制。
  • 連接管理:也就是三次握手和四次揮手,連接的可靠性是整體可靠性的前提,本文第二部分將會詳細介紹連接管理的內(nèi)容。
  • 流量控制:TCP 雙方都有固定大小的緩沖區(qū),流量控制的原理是利用滑動窗口控制數(shù)據(jù)發(fā)送速度,避免緩沖區(qū)溢出導(dǎo)致數(shù)據(jù)丟失。
  • 擁塞控制:TCP 利用慢啟動和擁塞避免等算法實現(xiàn)了擁塞控制。

上面為大家介紹了 TCP 最重要的三個特點,在本文第一部分的最后,再來看看 TCP 和 UDP 的對比吧。

1.4 TCP 和 UDP 的區(qū)別

二、TCP 的連接控制

2.1 建立連接

2.1.1 三次握手

TCP 的重要特性之一就是面向連接,連接雙方在發(fā)送數(shù)據(jù)之前必須經(jīng)歷握手的階段,那具體的過程是怎樣的呢?先來看圖,大家最好可以動手簡單畫畫這個圖,當(dāng)然還有后文四次揮手的圖,幫助加深記憶。



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

  • 第一次握手:客戶端向服務(wù)器發(fā)送報文段1,其中的 SYN 標(biāo)志位 (前文已經(jīng)介紹過各種標(biāo)志位的作用)的值為 1,表示這是一個用于請求發(fā)起連接的報文段,其中的序號字段 (Sequence Number,圖中簡寫為seq)被設(shè)置為初始序號x (Initial Sequence Number,ISN),TCP 連接雙方均可隨機選擇初始序號。發(fā)送完報文段1之后,客戶端進入 SYN-SENT 狀態(tài),等待服務(wù)器的確認。
  • 第二次握手:服務(wù)器在收到客戶端的連接請求后,向客戶端發(fā)送報文段2作為應(yīng)答,其中 ACK 標(biāo)志位設(shè)置為 1,表示對客戶端做出應(yīng)答,其確認序號字段 (Acknowledgment Number,圖中簡寫為小寫 ack) 生效,該字段值為 x + 1,也就是從客戶端收到的報文段的序號加一,代表服務(wù)器期望下次收到客戶端的數(shù)據(jù)的序號。此外,報文段2的 SYN 標(biāo)志位也設(shè)置為1,代表這同時也是一個用于發(fā)起連接的報文段,序號 seq 設(shè)置為服務(wù)器初始序號y。發(fā)送完報文段2后,服務(wù)器進入 SYN-RECEIVED 狀態(tài)。
  • 第三次握手:客戶端在收到報文段2后,向服務(wù)器發(fā)送報文段3,其 ACK 標(biāo)志位為1,代表對服務(wù)器做出應(yīng)答,確認序號字段 ack 為 y + 1,序號字段 seq 為 x + 1。此報文段發(fā)送完畢后,雙方都進入 ESTABLISHED 狀態(tài),表示連接已建立。
常見問題 1: TCP 建立連接為什么要三次握手而不是兩次?
  1. 防止已過期的連接請求報文突然又傳送到服務(wù)器,因而產(chǎn)生錯誤
    在雙方兩次握手即可建立連接的情況下,假設(shè)客戶端發(fā)送 A 報文段請求建立連接,由于網(wǎng)絡(luò)原因造成 A 暫時無法到達服務(wù)器,服務(wù)器接收不到請求報文段就不會返回確認報文段,客戶端在長時間得不到應(yīng)答的情況下重新發(fā)送請求報文段 B,這次 B 順利到達服務(wù)器,服務(wù)器隨即返回確認報文并進入 ESTABLISHED 狀態(tài),客戶端在收到 確認報文后也進入 ESTABLISHED 狀態(tài),雙方建立連接并傳輸數(shù)據(jù),之后正常斷開連接。此時姍姍來遲的 A 報文段才到達服務(wù)器,服務(wù)器隨即返回確認報文并進入 ESTABLISHED 狀態(tài),但是已經(jīng)進入 CLOSED 狀態(tài)的客戶端無法再接受確認報文段,更無法進入 ESTABLISHED 狀態(tài),這將導(dǎo)致服務(wù)器長時間單方面等待,造成資源浪費。

  2. 三次握手才能讓雙方均確認自己和對方的發(fā)送和接收能力都正常
    第一次握手:客戶端只是發(fā)送處請求報文段,什么都無法確認,而服務(wù)器可以確認自己的接收能力和對方的發(fā)送能力正常;
    第二次握手:客戶端可以確認自己發(fā)送能力和接收能力正常,對方發(fā)送能力和接收能力正常;
    第三次握手:服務(wù)器可以確認自己發(fā)送能力和接收能力正常,對方發(fā)送能力和接收能力正常;
    可見三次握手才能讓雙方都確認自己和對方的發(fā)送和接收能力全部正常,這樣就可以愉快地進行通信了。

  3. 告知對方自己的初始序號值,并確認收到對方的初始序號值
    TCP 實現(xiàn)了可靠的數(shù)據(jù)傳輸,原因之一就是 TCP 報文段中維護了序號字段和確認序號字段,也就是圖中的 seq 和 ack,通過這兩個字段雙方都可以知道在自己發(fā)出的數(shù)據(jù)中,哪些是已經(jīng)被對方確認接收的。這兩個字段的值會在初始序號值得基礎(chǔ)遞增,如果是兩次握手,只有發(fā)起方的初始序號可以得到確認,而另一方的初始序號則得不到確認。

常見問題2: TCP 建立連接為什么要三次握手而不是四次?

相比上個問題而言,這個問題就簡單多了。因為三次握手已經(jīng)可以確認雙方的發(fā)送接收能力正常,雙方都知道彼此已經(jīng)準(zhǔn)備好,而且也可以完成對雙方初始序號值得確認,也就無需再第四次握手了。

常見問題3: 有一種網(wǎng)絡(luò)攻擊是利用了 TCP 建立連接機制的漏洞,這個問題怎么解決?

在三次握手過程中,服務(wù)器在收到了客戶端的 SYN 報文段后,會分配并初始化連接變量和緩存,并向客戶端發(fā)送 SYN + ACK 報文段,這相當(dāng)于是打開了一個“半開連接 (half-open connection)”,會消耗服務(wù)器資源。

如果客戶端正常返回了 ACK 報文段,那么雙方可以正常建立連接,否則,服務(wù)器在等待一分鐘后會終止這個“半開連接”并回收資源。

這樣的機制為 SYN洪泛攻擊 (SYN flood attack)提供了機會,這是一種經(jīng)典的 DoS攻擊 (Denial of Service,拒絕服務(wù)攻擊),所謂的拒絕服務(wù)攻擊就是通過進行攻擊,使受害主機或網(wǎng)絡(luò)不能提供良好的服務(wù),從而間接達到攻擊的目的。

在 SYN 洪泛攻擊中,攻擊者發(fā)送大量的 SYN 報文段到服務(wù)器請求建立連接,但是卻不進行第三次握手,這會導(dǎo)致服務(wù)器打開大量的半開連接,消耗大量的資源,最終無法進行正常的服務(wù)。

解決辦法:
SYN Cookies,現(xiàn)在大多數(shù)主流操作系統(tǒng)都有這種防御系統(tǒng)。
SYN Cookies 是對 TCP 服務(wù)器端的三次握手做一些修改,專門用來防范 SYN 洪泛攻擊的一種手段。

它的原理是,在服務(wù)器接收到 SYN 報文段并返回 SYN + ACK 報文段時,不再打開一個半開連接,也不分配資源,而是根據(jù)這個 SYN 報文段的重要信息 (包括源和目的 IP 地址,端口號可一個秘密數(shù)),利用特定散列函數(shù)計算出一個 cookie 值。這個 cookie 作為將要返回的SYN + ACK 報文段的初始序列號(ISN)。當(dāng)客戶端返回一個 ACK 報文段時,服務(wù)器根據(jù)首部字段信息計算 cookie,與返回的確認序號(初始序列號 + 1)進行對比,如果相同,則是一個正常連接,然后分配資源并建立連接,否則拒絕建立連接。

2.2 關(guān)閉連接

2.2.1 四次揮手

建立一個連接需要三次握手,而終止一個連接要經(jīng)過 4次揮手。這由 TCP 的半關(guān)閉( half-close) 造成的。既然一個 TCP 連接是全雙工 (即數(shù)據(jù)在兩個方向上能同時傳遞), 因此每個方向必須單獨地進行關(guān)閉。這原則就是當(dāng)一方完成它的數(shù)據(jù)發(fā)送任務(wù)后就能發(fā)送一個 FIN 來終止這個方向連接。當(dāng)一端收到一個 FIN,它必須通知應(yīng)用層另一端已經(jīng)終止了數(shù)據(jù)傳送。理論上客戶端和服務(wù)器都可以發(fā)起主動關(guān)閉,但是更多的情況下是客戶端主動發(fā)起。



四次揮手詳細過程如下:

  1. 客戶端發(fā)送關(guān)閉連接的報文段,F(xiàn)IN 標(biāo)志位1,請求關(guān)閉連接,并停止發(fā)送數(shù)據(jù)。序號字段 seq = x (等于之前發(fā)送的所有數(shù)據(jù)的最后一個字節(jié)的序號加一),然后客戶端會進入 FIN-WAIT-1 狀態(tài),等待來自服務(wù)器的確認報文。
  2. 服務(wù)器收到 FIN 報文后,發(fā)回確認報文,ACK = 1, ack = x + 1,并帶上自己的序號 seq = y,然后服務(wù)器就進入 CLOSE-WAIT 狀態(tài)。服務(wù)器還會通知上層的應(yīng)用程序?qū)Ψ揭呀?jīng)釋放連接,此時 TCP 處于半關(guān)閉狀態(tài),也就是說客戶端已經(jīng)沒有數(shù)據(jù)要發(fā)送了,但是服務(wù)器還可以發(fā)送數(shù)據(jù),客戶端也還能夠接收。
  3. 客戶端收到服務(wù)器的 ACK 報文段后隨即進入 FIN-WAIT-2 狀態(tài),此時還能收到來自服務(wù)器的數(shù)據(jù),直到收到 FIN 報文段。
  4. 服務(wù)器發(fā)送完所有數(shù)據(jù)后,會向客戶端發(fā)送 FIN 報文段,各字段值如圖所示,隨后服務(wù)器進入 LAST-ACK 狀態(tài),等待來自客戶端的確認報文段。
  5. 客戶端收到來自服務(wù)器的 FIN 報文段后,向服務(wù)器發(fā)送 ACK 報文,隨后進入 TIME-WAIT 狀態(tài),等待 2MSL(2 * Maximum Segment Lifetime,兩倍的報文段最大存活時間) ,這是任何報文段在被丟棄前能在網(wǎng)絡(luò)中存在的最長時間,常用值有30秒、1分鐘和2分鐘。如無特殊情況,客戶端會進入 CLOSED 狀態(tài)。
  6. 服務(wù)器在接收到客戶端的 ACK 報文后會隨即進入 CLOSED 狀態(tài),由于沒有等待時間,一般而言,服務(wù)器比客戶端更早進入 CLOSED 狀態(tài)。
常見問題1: 為什么 TCP 關(guān)閉連接為什么要四次而不是三次?

服務(wù)器在收到客戶端的 FIN 報文段后,可能還有一些數(shù)據(jù)要傳輸,所以不能馬上關(guān)閉連接,但是會做出應(yīng)答,返回 ACK 報文段,接下來可能會繼續(xù)發(fā)送數(shù)據(jù),在數(shù)據(jù)發(fā)送完后,服務(wù)器會向客戶單發(fā)送 FIN 報文,表示數(shù)據(jù)已經(jīng)發(fā)送完畢,請求關(guān)閉連接,然后客戶端再做出應(yīng)答,因此一共需要四次揮手。

常見問題2: 客戶端為什么需要在 TIME-WAIT 狀態(tài)等待 2MSL 時間才能進入 CLOSED 狀態(tài)?

按照常理,在網(wǎng)絡(luò)正常的情況下,四個報文段發(fā)送完后,雙方就可以關(guān)閉連接進入 CLOSED 狀態(tài)了,但是網(wǎng)絡(luò)并不總是可靠的,如果客戶端發(fā)送的 ACK 報文段丟失,服務(wù)器在接收不到 ACK 的情況下會一直重發(fā) FIN 報文段,這顯然不是我們想要的。
因此客戶端為了確保服務(wù)器收到了 ACK,會設(shè)置一個定時器,并在 TIME-WAIT 狀態(tài)等待 2MSL 的時間,如果在此期間又收到了來自服務(wù)器的 FIN 報文段,那么客戶端會重新設(shè)置計時器并再次等待 2MSL 的時間,如果在這段時間內(nèi)沒有收到來自服務(wù)器的 FIN 報文,那就說明服務(wù)器已經(jīng)成功收到了 ACK 報文,此時客戶端就可以進入 CLOSED 狀態(tài)了。

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

3.1 什么是流量控制?

TCP 連接雙方的主機都為該連接設(shè)置了發(fā)送緩存和接收緩存,這些緩存起到了蓄水池的作用,我們肯定不能把上層應(yīng)用程序發(fā)來的數(shù)據(jù)一股腦兒發(fā)送到網(wǎng)絡(luò)中,而是利用發(fā)送緩存將其緩存起來,然后再按一定的速率通過網(wǎng)絡(luò)發(fā)送給對方,而接收緩存的作用是把對方傳來的數(shù)據(jù)先緩存起來,等到己方應(yīng)用程序有空的時候再來取走數(shù)據(jù)。示意圖如下:


TCP緩存示意圖

在此過程中,如果接收方應(yīng)用程序讀取數(shù)據(jù)的速度小于發(fā)送方的數(shù)據(jù)發(fā)送速度,將導(dǎo)致接收方的接收緩存溢出,造成數(shù)據(jù)丟失,這顯然不是我們想看到的。因此 TCP 為應(yīng)用程序提供了流量控制服務(wù) (flow-control service),以消除發(fā)送方使接收方的接收緩存溢出的可能性。簡單來說流量控制的目的就是協(xié)調(diào)發(fā)送方的數(shù)據(jù)發(fā)送速度,使其與接收方的數(shù)據(jù)處理速度相匹配,避免數(shù)據(jù)丟失,那么如何實現(xiàn)流量控制呢?

3.2 滑動窗口

首先給大家推薦一個視頻,講得很不錯 https://www.bilibili.com/video/av50251501
我們可以把發(fā)送方的發(fā)送緩存中的字節(jié)分為以下四類,每個編號對應(yīng)一個字節(jié):

發(fā)送緩存中的字節(jié)分類

  1. 第一類:已發(fā)送且已確認,這些數(shù)據(jù)已經(jīng)發(fā)送成功并已經(jīng)被確認的數(shù)據(jù),比如圖中的前31個bytes,這些數(shù)據(jù)其實的位置是在窗口之外了,下一步將被移出發(fā)送緩存。窗口內(nèi)順序最低的字節(jié)被確認之后,窗口左邊界會向右移動,稱為窗口合攏。
  2. 第二類:已發(fā)送但未收到確認,這部分?jǐn)?shù)據(jù)已經(jīng)被發(fā)送出去,但是還沒有收到接收端的 ACK,認為并沒有完成發(fā)送,這部分?jǐn)?shù)據(jù)屬于窗口內(nèi)的數(shù)據(jù)。
  3. 第三類:未發(fā)送但是接收方已經(jīng)準(zhǔn)備好接收,這部分是盡快發(fā)送的數(shù)據(jù),這部分?jǐn)?shù)據(jù)已經(jīng)被加載到緩存中,也在發(fā)送窗口中,正在等待發(fā)送,其實這個窗口是完全有接收方告知的,接收方告知當(dāng)前可以接受這些數(shù)據(jù),所以發(fā)送方需要盡快的發(fā)送。
  4. 第四類:未發(fā)送且接收方未準(zhǔn)備好接收,這些數(shù)據(jù)屬于未發(fā)送,同時接收端也不允許發(fā)送的,因為這些數(shù)據(jù)已經(jīng)超出了發(fā)送端所接收的范圍。
3.2.1 發(fā)送窗口和接收窗口
發(fā)送窗口

發(fā)送窗口:圖中的黑色框就是發(fā)送方的發(fā)送窗口,其大小由兩個因素決定:
1、接收方的提供的窗口大小 (TCP 報文段首部中的 window 字段),發(fā)送方在三次握手階段首次得到這個值,之后的通信過程中接收方會根據(jù)自己的可用緩存對這個值進行動態(tài)調(diào)整;
2、發(fā)送方會根據(jù)網(wǎng)絡(luò)情況維護一個擁塞窗口變量 (后文介紹)。發(fā)送窗口的大小取這兩個值的最小值。對于發(fā)送方來說,發(fā)送窗口分為兩部分,分別是已經(jīng)發(fā)送的部分(已經(jīng)發(fā)送了,但是沒有收到ACK)和可用窗口,接收端允許發(fā)送但是沒有發(fā)送的那部分稱為可用窗口。

接收窗口:對于接收端也是有一個接收窗口的,類似發(fā)送端,接收端的數(shù)據(jù)有3個分類,因為接收端并不需要等待ACK所以它沒有類似的接收并確認了的分類,情況如下

  1. Received and ACK Not Send to Process:這部分?jǐn)?shù)據(jù)屬于接收了數(shù)據(jù)但是還沒有被上層的應(yīng)用程序接收;
  2. Received Not ACK: 已經(jīng)接收,但是還沒有回復(fù) ACK;
  3. Not Received:有空位,還沒有被接收的數(shù)據(jù)。
3.2.2 滑動窗口是如何滑動的?
滑動窗口的滑動過程

累積確認概念:TCP 并不是每一個報文段都會回復(fù)一個 ACK ,可能會對兩個報文段發(fā)送一個ACK,也可能會對多個報文段發(fā)送 1 個 ACK,這稱為累積確認。比如說發(fā)送方有 1/2/3 3 個報文段,先發(fā)送了2,3 兩個報文段,但是接收方期望收到1報文段,這個時候 2/3 報文段就只能放在緩存中等待報文1的空洞被填上,如果報文段1一直不來,報文2/3也將被丟棄,如果報文1來了,那么會發(fā)送一個 ACK 對第3個報文段進行確認,就代表對這三個報文段全部進行了確認。

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

  1. 在握手過程中,接收方通告的窗口大小為20字節(jié),所以發(fā)送方將發(fā)送窗口大小設(shè)置為20字節(jié)。
  2. 從圖中的"上一個發(fā)送窗口的位置"(灰色虛線框)說起, 32-51號字節(jié)恰好處于發(fā)送窗口中,恰好20個字節(jié),假設(shè) TCP 將其分為 4 個報文段進行發(fā)送,每個報文段 5 個字節(jié)數(shù)據(jù),分別記為 seg1 32-36, seg2 37-41, seg3 42-46, seg4 47-51。
  3. TCP 將有序發(fā)送 seg1、seg2、seg3和seg4四個報文段,如果這四個報文段都順利到達接收方 (圖中并不是這樣),接收方將發(fā)回一個累積確認的 ACK 報文段,其中 ack = 52,代表希望收到下一個報文段的起始字節(jié)編號,報文段中也會繼續(xù)通告窗口大小,如果還是20字節(jié),那么發(fā)送方的窗口將整體向右移動20字節(jié),如果通告的窗口值變小,比如變成15,那么發(fā)送窗口左邊界移動20字節(jié),右邊界移動15字節(jié)。
  4. 如果在發(fā)送過程中 seg2 報文段丟失,而其他三個報文段正常到達接收方,那么接收方會先接受這三個報文段,然后返回 ACK 報文段,ack = 37,表示希望收到的下一個報文段的起始字節(jié)號為37,也就是seg2報文段。如果通告窗口值未發(fā)生變化,發(fā)送方在收到 ACK 后會將窗口整體右移5個字節(jié),也就變成了圖中的位置。
  5. 由于 seg2 還未收到 ACK,當(dāng)重傳計時器超時后,發(fā)送方會重新發(fā)送 seg2,此時52-56號字節(jié)又落到了發(fā)送窗口中,TCP 將其封裝成 報文段進行發(fā)送,如果接收方全部順利收到,會返回一個累積確認的 ACK,ack = 57,表示希望收到的下一個報文段的起始字節(jié)號為57。

接下來就是重復(fù)上述過程,直到 TCP 字節(jié)流的所有數(shù)據(jù)發(fā)送完畢。在這個過程中,接收方會根據(jù)自己接收緩存的剩余空間動態(tài)調(diào)整窗口值,對發(fā)送方進行流量控制。文字描述可能不夠直觀,大家可以參考上文推薦的視頻。

四、TCP 的擁塞控制

推薦視頻:https://www.bilibili.com/video/BV1L4411a7RN

4.1 什么是擁塞控制?

當(dāng)數(shù)據(jù)從一個大的管道 (比如一個快速局域網(wǎng))向一個較小的管道 (比如較慢的廣域網(wǎng))發(fā)送的時候就會發(fā)生擁塞,還有一種情況就是當(dāng)多個輸入流到達一個路由器,而路由器的輸出流小于這些輸入流的總和時,也會發(fā)生擁塞。舉個例子就好理解了,第一種情況就好像源源不斷的車流從八車道進入四車道,如果不進行控制,必然造成道路擁堵;第二種情況類似于很多車輛匯入十字路口,如果進的速度大于出的速度,再不加以控制,必然也會造成擁堵。于是 TCP 提供了響應(yīng)的機制來應(yīng)對這種情況,也就是 TCP 的擁塞控制。

4.2 如何實現(xiàn)擁塞控制?

TCP 一共使用了四種算法來實現(xiàn)擁塞控制:

  1. 慢開始 (slow-start);
  2. 擁塞避免 (congestion avoidance);
  3. 快速重傳 (fast retransmit);
  4. 快速恢復(fù) (fast recovery)。
    這里先介紹一下?lián)砣翱?(congestion window,簡寫為 cwnd)的概念:擁塞窗口是由發(fā)送方根據(jù)網(wǎng)絡(luò)狀況維護的一個變量,用于控制自己的數(shù)據(jù)發(fā)送速率。前文提到了發(fā)送方的發(fā)送窗口受兩個變量約束,一是接收方通告的窗口大小值,二就是發(fā)送方自身的擁塞窗口,實際的發(fā)送窗口大小取二者最小值。
4.2.1 慢開始(慢啟動)

在早期的 TCP Tahoe 版本中,只用到了慢開始和擁塞避免算法,如圖所示:



如圖所示,在剛開始,TCP 采用慢開始算法。慢開始不是指擁塞窗口的增長速度慢(增長速度是指數(shù)增長,非常快),而是指 TCP 開始發(fā)送設(shè)置 cwnd=1。
思路就是不要一開始就發(fā)送大量的數(shù)據(jù),先探測一下網(wǎng)絡(luò)的擁塞程度,也就是說由小到大 逐漸增加擁塞窗口的大小。這里用報文段的個數(shù)的擁塞窗口大小舉例說明慢啟動算法,實時擁塞窗口大小是以字節(jié)為單位的。為了防止 cwnd 增長過大引起網(wǎng)絡(luò)擁塞,設(shè)置一個慢開始門限(slow start threshold,簡寫為 ssthresh),初始值為16 ,

  • 當(dāng)cnwd < ssthresh,使用慢開始算法
  • 當(dāng) cnwd = ssthresh,既可使用慢開始算法,也可以使用擁塞避免算法
  • 當(dāng) cnwd > ssthresh,使用擁塞避免算法
4.2.2 擁塞避免

當(dāng)擁塞窗口大小達到初始 ssthresh 值時,轉(zhuǎn)而采用擁塞避免算法。
擁塞避免并非完全能夠避免擁塞,是說在擁塞避免階段將擁塞窗口控制為按線性規(guī)律增長,使網(wǎng)絡(luò)比較不容易出現(xiàn)擁塞。
思路:讓擁塞窗口 cwnd 緩慢地增大,即每經(jīng)過一個往返時間 RTT 就把發(fā)送方的擁塞窗口加一。無論是在慢開始階段還是在擁塞避免階段,只要發(fā)送方判斷網(wǎng)絡(luò)出現(xiàn)擁塞(其根據(jù)就是沒有收到確認,雖然沒有收到確認可能是其他原因的分組丟失,但是因為無法判定,所以都當(dāng)做擁塞來處理),就把慢開始門限設(shè)置為出現(xiàn)擁塞時的發(fā)送窗口大小的一半。然后把擁塞窗口設(shè)置為 1,執(zhí)行慢開始算法。

4.2.3 快速重傳
TCP Reno 應(yīng)用了四種算法

有時候的發(fā)送方未收到某個報文段的確認也并一定就說明一定是出現(xiàn)了網(wǎng)絡(luò)擁塞,也可能是其他原因,所以直接執(zhí)行慢開始算法會影響整體效率,后來的 TCP Reno 版本解決了這一問題,那就是采用快速重傳和快速恢復(fù)算法。

快速重傳要求接收方在收到一個失序的報文段后就立即發(fā)出重復(fù)確認(為的是使發(fā)送方及早知道有報文段沒有到達對方),而不要等到自己發(fā)送數(shù)據(jù)時捎帶確認??熘貍魉惴ㄒ?guī)定,發(fā)送方只要一連收到三個重復(fù)確認就應(yīng)當(dāng)立即重傳對方尚未收到的報文段,而不必繼續(xù)等待設(shè)置的重傳計時器時間到期。由于不需要等待設(shè)置的重傳計時器到期,能盡早重傳未被確認的報文段,能提高整個網(wǎng)絡(luò)的吞吐量。

4.2.4 快速恢復(fù)

當(dāng)發(fā)送方連續(xù)收到三個重復(fù)確認時,就執(zhí)行“乘法減小”算法,把 ssthresh 門限減半。 但是接下去并不執(zhí)行慢開始算法。考慮到如果網(wǎng)絡(luò)出現(xiàn)擁塞的話就不會收到好幾個重復(fù)的確認,所以發(fā)送方現(xiàn)在認為網(wǎng)絡(luò)可能沒有出現(xiàn)擁塞。所以此時不執(zhí)行慢開始算法,而是將 cwnd 設(shè)置為 ssthresh 的大小, 然后執(zhí)行擁塞避免算法。

五、TCP 粘包與拆包

5.1 TCP 粘包和拆包的原因

我們知道 TCP 是以字節(jié)流的方式傳輸數(shù)據(jù),傳輸?shù)淖钚挝粸橐粋€報文段(segment)。
TCP 首部 中有個選項 (Options)的字段,常見的選項為 MSS (Maximum Segment Size最大消息長度),它是收發(fā)雙方協(xié)商通信時每一個報文段所能承載的最大有效數(shù)據(jù)的長度。數(shù)據(jù)鏈路層每次傳輸?shù)臄?shù)據(jù)有個最大限制MTU (Maximum Transmission Unit),一般是1500字節(jié),超過這個量要分成多個報文段,MSS 則是這個最大限制減去 TCP 的首部,光是要傳輸?shù)臄?shù)據(jù)的大小,一般為1460字節(jié)。
MSS = MTU - Header

TCP 為提高性能,發(fā)送端會將需要發(fā)送的數(shù)據(jù)發(fā)送到發(fā)送緩存,等待緩存滿了之后,再將緩存中的數(shù)據(jù)發(fā)送到接收方。同理,接收方也有接收緩存這樣的機制,來接收數(shù)據(jù)。

上面這些是發(fā)生 TCP 粘包和拆包的前提,下面是具體的原因:

  • 要發(fā)送的數(shù)據(jù)大于TCP發(fā)送緩沖區(qū)剩余空間大小,將會發(fā)生拆包。
  • 待發(fā)送數(shù)據(jù)大于MSS(最大報文長度),TCP在傳輸前將進行拆包。
  • 應(yīng)用程序?qū)懭霐?shù)據(jù)小于剩余緩存大小,網(wǎng)卡將應(yīng)用多次寫入的數(shù)據(jù)先緩存起來,然后一起發(fā)送到網(wǎng)絡(luò)上,這將會發(fā)生粘包。
  • 接收數(shù)據(jù)端的應(yīng)用層沒有及時讀取接收緩存中的數(shù)據(jù),將發(fā)生粘包。

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

  • 設(shè)置定長消息,服務(wù)端每次讀取既定長度的內(nèi)容作為一條完整消息。
  • 設(shè)置消息邊界,數(shù)據(jù)結(jié)尾尾增加特殊字符分割。
  • 消息定義為tlv格式,即type-length-value,使用帶消息頭的協(xié)議,消息頭存儲消息開始標(biāo)識及消息長度信息,接收方獲取消息頭的時候解析出消息長度,然后向后讀取該長度的內(nèi)容。

轉(zhuǎn)載自

https://segmentfault.com/a/1190000022144695

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容