協議簇:TCP 解析: 建立連接

簡介

接前文 協議簇:TCP 解析: 基礎, 我們這篇文章來看看 TCP 連接建立的過程,也就是衆所周知的”三次握手“的具體流程.

三次握手

最普通的 TCP 握手流程如下圖:
三次握手
下面描述中,序列號對應於上圖中的行號.

  1. 初始狀態時,TCP A 處於連接關閉狀態, TCP B 處於監聽狀態. 也就是通常所說的 A 時 TCP 客戶段,B 是服務端.
  2. A 發送 SYN 給 B, 並附有 SEQ, 請求建立 TCP 連接。
    A 發送 SYN 後,狀態切換爲 SYN-SENT, B 接收到 A 發送的 SYN 後狀態切換爲 SYN-RECEIVED.
  3. B 收到 A 的 SYN 之後,發送它的 SYN(注意,這裏 A 和 B 的 SEQ 是相互獨立的 ),並附上 ACK 標記用以表明 B 收到了 A 的 SYN 包。
    這裏注意: B 發送的 ACK 的值爲 101,它代表 B 收到了序列號爲 100和100之前的所有字節數據,並告訴 A 自己期待下一次收到序列號以101開始的數據.
    A 在接收到 B 的 SYN 之後,狀態轉化爲 ESTABLISHED.
  4. A 收到 B 的 SYN 之後, 需要發送 ACK 給 B 告訴 B 自己收到了它的 SYN + ACK 包.
    這裏注意:A 發送的ACK的值爲 301, 原因是 B 的 SYN 中的 SEQ 是 300. A 發送的 SEQ 是 101,原因是上一次的請求中序列號已經增長到了 100. 下一個可用的序列號就是 101.
    在B接收到 A 的 ACK 之後,它的狀態切換爲 ESTABLSHED. 至此,三次握手已經完成,一個 TCP 連接已經成功建立。
  5. 在這條 TCP 連接上可以進行數據傳輸.

在瞭解了基本的流程之後,我們來使用 wireshark 包應用以下所學:

如下圖,忽略其中的黑色記錄,一共四條記錄,對應於上圖中的 2-5.
在這裏插入圖片描述
接下來,我們詳細看看每條記錄

客戶端 SYN

這是建立 TCP 連接三次握手中的第一次
在這裏插入圖片描述
這張圖涵蓋的信息很多,全部字段的含義在前文中已經描述過,這裏我們僅僅關注個別字段.

  1. Flags 字段中 SYN 標記爲 1. 表明當前 TCP 包是一個 SYN 包. 首先發送這個數據包的 TCP 段爲請求建立 TCP 連接的端點.
  2. Sequence Number 字段的值爲 2292773402. Wireshark 爲了方便我們查看,引入另外一個字段 relative sequence number. 這個字段的值是基於 initial sequence number 計算所得. 正如前文 TCP 基礎中所說,噹噹前 TCP 包是一個 SYN 包時,Sequence number 就是 Initial Sequence Number, 因此 這裏 relative sequence number 的值是 0.
服務端 SYN + ACK

這是建立 TCP 連接三次握手中的第二次
在這裏插入圖片描述

  1. Flags 字段中 SYN 和 ACK 字段均爲 1
  2. Sequence Number 字段的值爲 4127119125 (前面說過,客戶端的 SEQ 和 服務端的 SEQ 是獨立的,他們之間沒有聯繫), Relative Sequence Number 爲 0.
  3. Acknowledge Number 字段的值爲 2292773403. 客戶段發送的 SYN 中 的 SEQ 值是 2292773402. 序列號 2292773403 告訴客戶端 2292773403以及之前的所有數據已經收到。 在接收到這個響應之後, 客戶端便可以確信服務段收到了自己發送的 SYN 包.
客戶端 ACK

這是建立 TCP 連接三次握手中的第三次
在這裏插入圖片描述

  1. Flags 字段 ACK 爲 1
  2. Sequence Number 的值爲 2292773403. 這個值於服務端發送給我們 SYN+ACK 包中的 ACK 值相同.
  3. Acknowledge Number 的值爲 4127119126. 這裏需要注意服務第發送的 SYN+ACK 包中的 SEQ 的值爲 4127119125.

至此,這個 TCP 連接成功建立。

特殊情況: 雙方同時請求建立連接

在一個 TCP 建立成功之前,連接雙方是沒有任何關於對方的信息的。因此,存在一種巧合,那就是雙方同時發起建立TCP連接的請求. 這裏我們來看一下這個特殊情況.
在這裏插入圖片描述
圖中 "… " 用來表示該數據包正在網絡上傳輸,對方還未收到.

注意,這裏圖中雖然對數據包進行 1-7 的編號,但是對於雙方任何一方,都是獨立的. 也就是說,接收數據的先後是不確定的,有可能 B 先接收到 A 的包,也有可能 A 先接受到 B 的包。 這裏我們並沒有明確的指定哪一方爲 服務端,哪一方爲客戶端,因此這裏的結論都是成立的.

  1. 初始狀態下,雙方均處於 CLOSED 狀態. 存在這種可能: 某一時刻雙發同時發送自己的 SYN 給對方,請求建立一條 TCP 連接.
  2. 在某一時間點,A 發送 SYN 到 B 請求建立 TCP 連接. 此時,A 的狀態切換爲 SYN-SENT
  3. B 在未收到 A 發送的 SYN 包時,發送了自己的 SYN 給 A,請求建立一條連接. 此時,B 的狀態也切換爲 SYN-SENT. 在接收到 B 發送的 SYN 之後,A 的狀態切換爲 SYN-RECEIVED.
  4. 此時,A 發送的 SYN 到達 B 端。B 收到了 A 發送的 SYN 包,狀態切換爲 SYN-RECEIVED.
  5. 在第三步之後,A的狀態一旦變成SYN-RECEIVED, 他就需要發送對應的 SYN+ACK 給 B,以確認自己接收到了 B 發送的 SYN. 並將自己的狀態切換爲 ESTABLISHED. 這裏注意,它發送完 SYN+ACK 之後,只是單方的進入 ESTABLISHED 狀態,對應狀態依然爲 SYN-RECEIVED.
  6. 在第四步之後, B的狀態一旦變成SYN-RECEIVED,它也要發送 SYN+ACK. 並切換狀態爲 ESTABLISHED。
  7. B 收到 A 發送的 SYN+ACK。 至此,雙方均進入 ESTABLISHED 狀態,一個連接已然建立成功.

特殊情況: 舊的重複的 SYN

TCP 協議的設計爲三次握手的一個很重要原因就是處理舊的重複的SYN包

RFC793 原文: The principle reason for the three-way handshake is to prevent old
duplicate connection initiations from causing confusion

這裏我們就來看看它是如何處理的。

看下圖的 TCP 包流程:
在這裏插入圖片描述

  1. 初始狀態下, A 爲客戶端,處於 CLOSED 狀態。 B 爲服務端,處於LISTEN狀態
  2. 某一時刻,A 發送 SYN 給 B,請求建立 TCP 連接
  3. B 在收到 A 剛剛發送的 SYN 包之前, 收到了一箇舊的重複的來自 A 的 SYN.
  4. 對於 B 段來說,在收到來自 A 的 SYN 包時,它是不知道那是一箇舊的重複的 SYN 包的,因此它就想就受到一個普通的 SYN一樣響應這個 SYN 包. 發送 SYN+ACK 進行確認.
  5. A 端收到來自 B 段的 SYN+ACK, 發現其中的 ACK 字段的值不正確. 因爲自己發送的 SYN 中 SEQ 值爲 100,響應SYN+ACK包中的 ACK的值應該爲 101,但是它收到的 SYN+ACK包中的ACK的值卻是91. 在收到這個非法的SYN+ACK之後,A 段發送 RST,並附上錯誤的SEQ序列號. B 端收到A發送過來的 RST 之後,便得知 A 重置了上一個SYN想要建立的TCP連接,B 段重置所有關於爲上一個 SYN 所記錄的狀態,並重新回到 LISTEN 狀態.
  6. 對於 TCP 協議來說,數據是不會丟失的,也就是說 B 段遲早會收到 A 發送的 SYN(SEQ=100). B 端收到 A 的 RST 之後,狀態切換到了 LISTEN之後, A 發送 SYN(SEQ=100)到達,此時B依然回像通常情況一樣. 如果 SYN(SEQ=100)到達 B 段早於 A 發送的 RST,將會是一個更復雜的情況,涉及到雙發均發送 RST 的場景. 這裏能力有限,略掉.
  7. B 在收到正確的 SYN+ACK之後,發送自己的 SYN 給 A
  8. A 在發送自己的 SYN+ACK給B,這樣之後,一個 TCP 連接成功建立.

如果剔除上述流程中 3-5,我們會發現這就是一個普通的 TCP 連接的三次握手流程. 厲害的地方在於,引入三次握手建立連接的機制,TCP 可以優雅的處理掉由於網絡不可靠所導致的非法SYN的數據包。 厲害👍

Half-Open connections

這裏我們將 “Half-Open connection” 稱作 “半連接狀態的鏈接”.

什麼是半連接狀態的鏈接?
B方鏈接已經關閉或者由於其他原因而奔潰掉,但是A方不知道B方的情況,此時 A 方持有的就是一個半連接狀態的鏈接.

我們通過一個例子來描述半連接狀態的鏈接
在這裏插入圖片描述

  1. 在某一時刻,A段的鏈接崩潰掉。假設奔潰的原因是A端物理內存不足. B 端未收到任何通知
  2. 在A端從錯誤中回覆過來之後,(假設)用戶嘗試發送數據從原先的 TCP 連接上,A 端TCP模塊會返回類似的 “connection not open”錯誤,此時 A 端TCP狀態爲 CLOSED, B 端不知道 A 端的情況,因此狀態依然爲 ESTABLISHED.
  3. A 端發現 TCP 錯誤後,嘗試重新建立原先的 TCP 鏈接,發送SYN到 B 端
  4. B 端收到 A 的 SYN 之後,發現收到的 SEQ(400) 於自己期待的 SEQ(100) 無法匹配, 因此不對這個 SYN 進行確認,而是發送自已認爲正確的 SEQ(100) 給 A 端.
  5. A 端收到 B 端發送的 SYN+ACK 之後,發現收到的 SEQ(100) 與自己剛剛發送的 SYN 中的 SEQ(400)不一致,因此得知自己的TCP狀態已經與B端不一致了。因此,發送 RST 重置當前的 TCP 鏈接.
  6. 在上述步驟之後,上一個 TCP 鏈接依然被重置,雙方不再維護上一個TCP鏈接的任何狀態.
  7. 這裏 A 端重新發送 SYN 請求建立一個全新的 TCP 鏈接. 至此,已經走上了正路,在正常的三次握手之後,一個新的 TCP 鏈接即將建立.
另外一種情況#####

這裏還有一個更有趣的情況. 考慮下圖的形況
在這裏插入圖片描述

  1. A 端已然崩潰掉. B 端對此一無所知.
  2. 此時, B 端認爲鏈接一切正常,在該TCP鏈接上往 A 發送數據.
  3. A 端收到這個數據之後,發現這個 TCP 鏈接並不存在,因此直接發送 RST 通知對方重置鏈接.

Reset

何時 TCP 會發送 RST?
通常來說,當 TCP 接收到一個明顯不是給當前連接的數據時,必須發送 RST。 當不是很明顯時,不應該發送 RST.

發送 RST的原則:#####
  1. 噹噹前 TCP 連接不存在時,對方發送任何數據到此(除過RST),我們會發送 RST.

    在這種情況下,如果從對方收到的數據包是包含ACK,那麼 RST 中的 SEQ 會被置爲 ACK 中包含的 SEQ,其他情況下 RST 中的 SEQ 會被置爲0且 ACK 字段被置爲 SEQ + 接收到的數據的長度. 連接狀態維持爲 CLOSED.

  2. 噹噹前 TCP 連接處於正在建立的狀態(LISTEN,SYN-SENT 或 SYN-RECIEVED, RFC中成爲 非同步狀態), 接收到的數據卻包含了非法的 ACK 值,我們會發送 RST.

  3. 噹噹前 TCP 處於同步的狀態(ESTABLISHED, FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT), 此時收到一個非法的TCP數據包(錯誤的 SEQ或者錯誤的 ACK值), 我們應該發送一個ACK,其中包含當前的發送SEQ和一個 ACK值表明我們期待收到的下一個TCP數據包的序列號. 此時,維持當前 TCP 的狀態不變.

    這種情況的一個例子就是我們在半連接狀態的連接小節中第一張圖的 步驟 4.

END!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章