TCP 詳解


1. TCP報文簡介

TCP 是一種面向連接的可靠的字節流服務

可靠性體現在:

  • 應用數據被分割成 TCP 認為最適合發生的數據塊。由 TCP 傳遞給 IP 的信息單位稱為報文段
  • 當 TCP 發出一個段后,它啟動一個定時器,等待目的端確認收到這個報文段。如果不能及時收到一個確認,將重發這個報文段。
  • 當 TCP 收到發自 TCP 連接另一端的數據,它將發送一個確認。這個確認不是立即發送,通常將推遲幾分之一秒。
  • TCP 將保持它首部和數據的檢驗和。這是一個端到端的檢驗和,目的是檢測數據在傳輸過程中的任何變化。如果收到段的檢驗和有差錯, TCP 將丟棄這個報文段和不確認收到此報文段(希望發端超時並重發)。
  • 如果必要, TCP 將對收到的數據進行重新排序,將收到的數據以正確的順序交給應用層。
  • TCP 的接收端必須丟棄重復的數據。
  • TCP 還能提供流量控制。 TCP連接的每一方都有固定大小的緩沖空間。 TCP 的接收端只允許另一端發送接收端緩沖區所能接納的數據。這將防止較快主機致使較慢主機的緩沖區溢出。

TCP 數據被封裝在一個 IP 數據報中:

如下圖所示,為 TCP 首部的數據格式。如果不計入任選字段,它通常是 20 個字節。

  • URG:緊急指針有效
  • ACK:確認序號有效
  • PSH:接收方應該盡快將這個報文段交給應用層
  • RST:重新連接
  • SYN:同步序號用來發起一個連接
  • FIN:發端完成發生任務

2. 三次握手

建立連接過程:

  • 客戶端發送一個 SYN 段指明客戶端打算連接的服務器的端口,以及初始序列號(ISN,Initial Sequence Number,如圖所示為 1415531521)。這個 SYN 段為報文段1。
  • 服務器發回包含服務器的初始序號的 SYN 報文段(報文段2)作為應答。同時,將確認序號設置為客戶的 ISN 加 1 以對客戶的 SYN 報文段進行確認。一個 SYN 將占用一個序號。
  • 客戶必須將確認序號設置為服務器的 ISN 加 1 以對服務器的 SYN 報文段進行確認(報文段3)。

發送第一個 SYN 的一端將執行主動打開。接受這個 SYN 並發回下一個 SYN 的另一端執行被動打開。

ISN 初始序列號是否固定?

ISN 隨時間而變化,因此每個連接都將具有不同的 ISN。ISN可以看作是一個 32 bit的計數器,每 4ms 加 1。這樣選擇序號的目的在於防止在網絡中被延遲的分組在以后又被傳送,而導致某個連接的一方對它做錯誤的解釋

三次握手是否可以攜帶數據?

三次握手過程中僅僅可以在第三次握手中攜帶數據,其中一個簡單的原因就是會讓服務器更加容易受到攻擊了。而對於第三次的話,此時客戶端已經處於 ESTABLISHED 狀態。對於客戶端來說,他已經建立起連接了,並且也已經知道服務器的接收、發送能力是正常的了,所以能攜帶數據是可以的。

簡述 SYN 攻擊。

服務器端的資源分配是在二次握手時分配的,而客戶端的資源是在完成三次握手時分配的,所以服務器容易受到 SYN 洪泛攻擊。SYN 攻擊就是客戶端在短時間內偽造大量不存在的 IP 地址,並向服務器不斷地發送 SYN 包,服務器則回復確認包,並等待客戶端確認,由於源地址不存在,因此服務器需要不斷重發直至超時,這些偽造的 SYN 包將長時間占用未連接隊列,導致正常的 SYN 請求因為隊列滿而被丟棄,從而引起網絡擁塞甚至系統癱瘓。SYN 攻擊是一種典型的 DoS/DDoS 攻擊。

思考如下問題:為什么是三次握手,而非兩次?

首先三次握手的目的是為了確認客戶端與服務器的收包能力和發包能力如何,如下所示:

  • 第一次握手是為了確認客戶端的發包能力以及服務器的收包能力
  • 第二次握手是為了確認客戶端的收包能力以及服務器的發包能力
  • 第三次握手是為了讓服務器知道客戶端的收包能力是正常的

倘若是兩次握手,那服務器將無法得知客戶端的收包能力如何,如下圖所示:

(1)首先,客戶端發送連接請求,但是由於連接請求報文在網絡中長時間滯留而未及時到達服務器端,服務器端無法向客戶端發送確認報文,於是客戶端又重新發送連接請求,這次成功到達服務器端,並成功建立連接,如下圖所示:

(2)客戶端向服務器發送斷開連接的請求,通過四次揮手,客戶端與服務器斷開連接,而那個在網絡中滯留的數據包依然還未到達服務器端,如下圖所示:

(3)客戶端與服務器成功斷開連接后,在網絡中滯留的數據包成功到達服務器端,服務器以為客戶端又要建立連接請求,故向客戶端發送了確認報文,但是客戶端此時處於連接關閉狀態,不會對服務器發來的確認報文進行應答,同時服務器也在一直等待客戶端的應答,這就造成了資源的浪費,如下圖所示:

3. 四次揮手

建立一個連接需要三次握手,而終止一個連接需要 4 次握手。這是由 TCP 的半關閉造成的。既然一個 TCP 連接是全雙工(即數據在兩個方向上能同時傳遞),因此每個方向必須單獨地進行關閉。這原則就是當一方完成它的數據發送任務后就能發送一個 FIN 來終止這個方向連接。當一端收到一個 FIN ,它必須通知應用層另一端幾經終止了那個方向的數據傳送。發送 FIN 通常是應用層進行關閉的結果。

斷開連接過程:

  • 客戶端向服務器端發送一個 FIN,用來關閉從客戶端到服務器的數據傳輸,以及 ACK 段。
  • 服務器收到這個 FIN,它發回一個 ACK,確認序號為收到的序號加 1(報文段5)。和 SYN 一樣,一個 FIN 將占用一個序號。同時,TCP 服務器還向應用程序傳輸一個文件結束符。
  • 接着,這個服務器程序就關閉它的連接,導致它的 TCP 端發送一個 FIN(報文段6),客戶必須發回一個確認,並將確認序號設置為收到的序號加 1(報文段7)

同樣思考:為什么要四次才可以斷開連接?

當客戶端向服務器端發送 FIN 報文請求關閉連接,這只能表明客戶端不想再發數據了,但不能表明服務器端想關閉連接,如果只用 2 次就可以斷開連接,那么服務器端給客戶端的數據包還未發完雙方就斷開了連接,就會出現資源丟失。而 4 次揮手中的后 2 次就是為了確認服務器不想再發送數據了。

4. TCP 狀態遷移圖

TIME_WAIT 狀態也稱為 2MSL 等待狀態。每個具體的 TCP 實現必須選擇一個報文段最大生存時間 MSL。它是任何報文段被丟棄前在網絡中生存的最長時間。那么 TIME_WAIT 存在的意義是什么?

TCP 協議在關閉連接的四次握手過程中,最終的 ACK 確認報文是由主動關閉連接的一端發出的,通常是客戶端,如果這個 ACK 在網絡中丟失,服務器端沒有收到客戶端發來的確認報文,將會重發最終的 FIN 報文,因此客戶端必須維護狀態信息允許它自己重發最終的 ACK 確認報文。由於一個報文段的最大生存時間是 MSL,那么第一個 MSL 是為了讓客戶端發出的 ACK 報文正確到達服務器端,第二個 MSL 是為了接收服務器因為沒有收到 ACK 而重新發送的 FIN 報文。如果客戶端在 TIME_WAIT 后沒有收到重新發來的 FIN 報文,說明服務器正確接收了 ACK 報文,至此連接正式關閉。

5. 同時打開與同時關閉

一個同時打開的連接需要交換 4個報文段,比正常的三次握手多一個。

當出現同時打開的情況時,狀態變遷與第 3 小節的圖示不同。兩端幾乎在同時發送 SYN,並進入 SYN_SENT 狀態。當每一端收到 SYN 時,狀態變為 SYN_RCVD,同時它們都再發 SYN 並對收到的 SYN 進行確認。當雙方都收到 SYN 及相應的 ACK時,狀態都變遷為 ESTABLISHED。

如上圖所示,當應用層發出關閉命令時,兩端均從 ESTABLISHED 變為 FIN_WAIT_1。這將導致雙方各發送一個 FIN,兩個 FIN 經過網絡傳送后分別到達另一端。收到 FIN 后,狀態由 FIN_WAIT_1 變遷到 CLOSING,並發送最后的 ACK。當收到最后的 ACK 時,狀態變化為 TIME_WAIT。

6. 滑動窗口

在這個圖中,我們將字節從 1 至 11 進行標號。接收方通告的窗口稱為提供的窗口,它覆蓋了從第 4 字節到第 9 字節的區域,表明接收方已經確認了包括第 3 字節在內的數據,且通告窗口大小為 6。

當接收方確認數據后,這個滑動窗口不時地向右移動。窗口兩個邊沿的相對運動增加或減少了窗口的大小。

  • 稱窗口左邊沿向右邊沿靠近為窗口合攏。這種現象發生在數據被發送和確認時。
  • 當窗口右邊沿向右移動時將允許發送更多的數據,我們稱之為窗口張開。這種現象發生在另一端的接收進程讀取已經確認的數據並釋放了 TCP 的接收緩存時。
  • 當右邊沿向左移動時,我們稱之為窗口收縮

因為窗口的左邊沿受另一端發送的確認序號的控制,因此不可能向左邊移動。如果接收到一個指示窗口左邊沿向左移動的 ACK,則它被認為是一個重復 ACK,並被丟棄。如果左邊沿到達右邊沿,則稱其為一個零窗口,此時發送方不能夠發送任何數據。

如下圖所示,為數據傳輸過程中滑動窗口的動態變化圖:

從圖中可以看出:

  • 發送方不必發送一個全窗口大小的數據。
  • 來自接收方的一個報文段確認數據並把窗口向右移動。這是因為窗口的大小是相對於確認序號的。
  • 如圖報文段 7 到報文段 8 中變化的那樣,窗口的大小可以減小,但是窗口的右邊沿卻不能向左移動。
  • 接收方在發送一個 ACK 前不必等待窗口被填滿。

7. 超時重傳

TCP 服務必須能夠重傳超時時間內未收到確認的 TCP 報文段。為此,TCP 模塊為每個 TCP 報文段都維護一個重傳定時器,該定時器在 TCP 報文段第一次被發送時啟動。如果超時時間內未收到接收方的應答,TCP 模塊將重傳 TCP 報文段並重置定時器。在達到一定次數還沒有成功時放棄並發送一個復位信號。

其中比較重要的概念就是重傳超時時間,即RTO

8. 擁塞控制

TCP 模塊還有一個重要的任務,就是提高網絡利用率,降低丟包率,並保證網絡資源對每條數據流的公平性。這就是所謂的擁塞控制

擁塞控制分為四個部分:

  • 慢啟動
  • 擁塞避免
  • 快速重傳
  • 快速恢復

擁塞控制的最終受控變量是發送端向網絡一次連續寫入的數據量,稱為 SWND(Send Window,發送窗口)。不過,發送端最終以 TCP 報文段來發送數據,所以 SWND 限定了發送斷能連續發送的 TCP 報文段數量。這些 TCP 報文段的最大長度(僅指數據部分)稱為 SMSS。

發送端需要合理選擇 SWND 的大小。如果 SWND 太小,會引起明顯的網絡延遲;反之如果太大,則容易發生網絡擁堵。接收方可以通過其接受通告窗口(RWND)來控制發送端的 SWND。但其顯然不夠,所以發送端引入了一個稱為擁塞窗口(CWND)的狀態變量。如圖所示:

7.1 慢啟動和擁塞避免

慢啟動為發送方的 TCP 增加了另一個窗口:擁塞窗口,記為 CWND。

當與另一個網絡的主機建立 TCP 連接時,擁塞窗口被初始化為 1 個報文段(即另一端通告的報文段大小)。每收到一個 ACK,擁塞窗口就增加一個報文段( CWND 以字節為單位,但是慢啟動以報文段大小為單位進行增加)。發送方取擁塞窗口與通告窗口中的最小值作為發送上限。擁塞窗口是發送方使用的流量控制,而通告窗口則是接收方使用的流量控制。

發送方開始時發送一個報文段,然后等待 ACK。當收到該 ACK 時,擁塞窗口從1增加為 2,即可以發送兩個報文段。當收到這兩個報文段的 ACK 時,擁塞窗口就增加為 4。這是一種指數增加的關系。

慢啟動算法的理由是:TCP 模塊剛開始發送數據時並不知道網絡的實際情況,需要用一種試探的方式平滑地增加 CWND 的大小。

但如果不施加其他手段,慢啟動必然使得 CWND 快速膨脹,並最終導致網絡擁塞。因此 TCP 擁塞控制中定義了另一個很重要的狀態變量:慢啟動門限(ssthresh)。當 CWND 的大小超過該值時,TCP 擁塞控制將進入擁塞避免階段。

  • 標號 1 處,TCP 初始連接進行數據交換,開始慢啟動,初始 CWND = IW = 1 ,ssthresh = 16,在傳輸輪次 0~4 階段進行慢啟動階段,CWND 按照 1-2-4-8-16 的順序進行指數增長
  • 標號 2 處,CWND = 16 = ssthresh,此時觸發擁塞避免過程,開始線性增長,在傳輸輪次 4~12 階段,CWND 按照 16-17-18-19-20-21-22-23-24 進行線性增長
  • 標號 3 處,TCP 發生了 RTO 重傳,認為網絡發生擁塞,於是設置 ssthresh = CWND / 2 = 12,CWND = 1 重新進行慢啟動過程
  • 標號 4 處,TCP 從 CWND = 1 開始重新開始慢啟動過程
  • 標號 5 處,當 CWND = 12 時改為執行擁塞避免算法,擁塞窗口按照線性規律增長,沒經過一個往返時延就增加一個 MSS 的大小

對於慢啟動門限以及發生網絡擁塞時為何要降為其一半的原因詳見這篇文章:TCP核心概念-慢啟動、擁塞避免、慢啟動門限的真實含義

7.2 快速重傳和快速恢復

在很多情況下,發送端都可能接收到重復的確認報文段,比如 TCP 報文段丟失,或者接收端收到亂序 TCP 報文段並重排之等。擁塞控制算法需要判斷當受到重復的確認報文段時,網絡是否真的發生了擁塞,或者說 TCP 報文段是否真的丟失了。

具體做法是:發送端如果連續收到 3 個重復的確認報文段,就認為是擁塞發生了。然后它啟用快速重傳和快速恢復算法來處理擁塞,過程如下:

  • 當收到第 3 個重復的確認報文段時,按照下式計算 ssthresh,然后立即重傳丟失的報文段

\[ssthresh = max(FlightSize/2, 2*SMSS) \]

  • 並按照下式設置 CWND

\[CWND = ssthresh + 3*SNSS \]

  • 每次收到一個重復的確認時,設置 \(CWND = CWND + SMSS\)。此時發送端可以發送新的 TCP 報文段
  • 當收到新數據的確認時,設置 \(CWND = ssthresh\),ssthresh 是新的慢啟動門限值,由第一步計算得到

快速重傳和快速恢復完成后,擁塞控制將恢復到擁塞避免階段。

9. 參考文獻

《TCP/IP詳解》
《Linux高性能服務器編程》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM