TCP 序列號和確認號是如何變化的?

2022-10-26 18:00:08

大家好,我是小林。

網站上回答了很多人的問題,我發現很多人對 TCP 序列號和確認號的變化都是懵懵懂懂的,只知道三次握手和四次揮手過程中,ACK 報文中確認號要 +1,然後資料傳輸中 TCP 序列號和確認號的變化就不知道了。

也有很多同學跟我反饋,希望我寫一篇關於 TCP 序列號和確認號變化過程的文章。大家別小看這個基礎知識點,其實很多人都不知道的。

所以,這次就跟大家聊聊以下過程中,TCP 序列號和確認號是如何變化的?

  • 三次握手中 TCP 序列號和確認號的變化
  • 資料傳輸中 TCP 序列號和確認號的變化
  • 四次揮手中 TCP 序列號和確認號的變化

萬能公式

我根據經驗總結了一條萬能公式。

傳送的 TCP 報文:

  • 公式一:序列號 = 上一次傳送的序列號 + len(資料長度)。特殊情況,如果上一次傳送的報文是 SYN 報文或者 FIN 報文,則改為 上一次傳送的序列號 + 1。
  • 公式二:確認號 = 上一次收到的報文中的序列號 + len(資料長度)。特殊情況,如果收到的是 SYN 報文或者 FIN 報文,則改為上一次收到的報文中的序列號 + 1。

可能有點抽象,接下來舉一些實際的場景,加深對這個萬能公式的理解。

先給大家看看 TCP 序列號和確認號在 TCP 頭部的哪個位置。可以看到,這兩個欄位都是 32 位。

在這裡插入圖片描述在這裡插入圖片描述

這裡重點關注這三個欄位的作用:

  • 序列號:在建立連線時由核心生成的亂數作為其初始值,通過 SYN 報文傳給接收端主機,每傳送一次資料,就「累加」一次該「資料位元組數」的大小。用來解決網路包亂序問題。
  • 確認號:指下一次「期望」收到的資料的序列號,傳送端收到接收方發來的 ACK 確認報文以後,就可以認為在這個序號以前的資料都已經被正常接收。用來解決丟包的問題。
  • 控制位:用來標識 TCP 報文是什麼型別的報文,比如是 SYN 報文、資料包文、ACK 報文,FIN 報文等。

三次握手階段的變化

先來說說三次握手中 TCP 序列號和確認號的變化。

假設使用者端的初始化序列號為 client_isn,伺服器端的初始化序列號為 server_isn,TCP 三次握手的流程如下:

在這裡插入圖片描述在這裡插入圖片描述

在這裡我們重點關注,下面這兩個過程。

伺服器端收到使用者端的 SYN 報文後,會將 SYN-ACK 報文(第二次握手報文)中序列號和確認號分別設定為:

  • 序列號設定為伺服器端隨機初始化的序列號 server_isn。
  • 確認號設定為 client_isn + 1,伺服器端上一次收到的報文是使用者端發來的 SYN 報文,該報文的 seq = client_isn,那麼根據公式 2(_確認號 = 上一次收到的報文中的序列號 + len。特殊情況,如果收到的是 SYN 報文或者 FIN 報文,則改為 + 1_),可以得出當前確認號 = client_isn + 1。

使用者端收到伺服器端的 SYN-ACK 報文後,會將 ACK 報文(第三次握手報文)中序列號和確認號分別設定為:

  • 序列號設定為 client_isn + 1。使用者端上一次傳送報文是 SYN 報文,SYN 的序列號為 client_isn,根據公式 1(_序列號 = 上一次傳送的序列號 + len。特殊情況,如果上一次傳送的報文是 SYN 報文或者 FIN 報文,則改為 + 1_),所以當前的序列號為 client_isn + 1。
  • 確認號設定為 server_isn + 1,使用者端上一次收到的報文是伺服器端發來的 SYN-ACK 報文,該報文的 seq = server_isn,那麼根據公式 2(_確認號 = 收到的報文中的序列號 + len。特殊情況,如果收到的是 SYN 報文或者 FIN 報文,則改為 + 1_),可以得出當前確認號 = server_isn + 1。

為什麼第二次和第三次握手報文中的確認號是將對方的序列號 + 1 後作為確認號呢?

SYN 報文是特殊的 TCP 報文,用於建立連線時使用,雖然 SYN 報文不攜帶使用者資料,但是 TCP 將 SYN 報文視為 1 位元組的資料,當對方收到了 SYN 報文後,在回覆 ACK 報文時,就需要將 ACK 報文中的確認號設定為 SYN 的序列號 + 1 ,這樣做是有兩個目的:

  • 告訴對方,我方已經收到 SYN 報文。
  • 告訴對方,我方下一次「期望」收到的報文的序列號為此確認號,比如使用者端與伺服器端完成三次握手之後,伺服器端接下來期望收到的是序列號為 client_isn + 1 的 TCP 資料包文。

資料傳輸階段的變化

完成了,三次握手後,使用者端就可以傳送第一個 ** **TCP 資料包文了,假設使用者端即將要傳送 10 位元組的資料,流程圖下:

在這裡插入圖片描述在這裡插入圖片描述

使用者端傳送 10 位元組的資料,通常 TCP 資料包文的控制位是 [PSH, ACK],此時該 TCP 資料包文的序列號和確認號分別設定為:

  • 序列號設定為 client_isn + 1。使用者端上一次傳送報文是 ACK 報文(第三次握手),該報文的 seq = client_isn + 1,由於是一個單純的 ACK 報文,沒有攜帶使用者資料,所以 len = 0。根據公式 1(_序列號 = 上一次傳送的序列號 + len_),可以得出當前的序列號為 client_isn + 1 + 0,即 client_isn + 1。
  • 確認號設定為 server_isn + 1。沒錯,還是和第三次握手的 ACK 報文的確認號一樣,這是因為使用者端三次握手之後,傳送 TCP 資料包文 之前,如果沒有收到伺服器端的 TCP 資料包文,確認號還是延用上一次的,其實根據公式 2 你也能得到這個結論。

可以看到,使用者端與伺服器端完成 TCP 三次握手後,傳送的第一個 「TCP 資料包文的序列號和確認號」都是和「第三次握手的 ACK 報文中序列號和確認號」一樣的

接著,當伺服器端收到使用者端 10 位元組的 TCP 資料包文後,就需要回復一個 ACK 報文,此時該報文的序列號和確認號分別設定為:

  • 序列號設定為 server_isn + 1。伺服器端上一次傳送報文是 SYN-ACK 報文,序列號為 server_isn,根據公式 1(_序列號 = 上一次傳送的序列號 + len。特殊情況,如果上一次傳送的報文是 SYN 報文或者 FIN 報文,則改為 + 1_),所以當前的序列號為 server_isn + 1。
  • 確認號設定為 client_isn + 11 。伺服器端上一次收到的報文是使用者端發來的 10 位元組 TCP 資料包文,該報文的 seq = client_isn + 1,len = 10。根據公式 2(_確認號 = 上一次收到的報文中的序列號 + len_),也就是將「收到的 TCP 資料包文中的序列號 client_isn + 1,再加上 10(len = 10) 」的值作為了確認號,表示自己收到了該 10 位元組的資料包文。

之前有讀者問,如果使用者端傳送的第三次握手 ACK 報文丟失了,處於 SYN_RCVD 狀態伺服器端收到了使用者端第一個 TCP 資料包文會發生什麼?

剛才前面我也說了,傳送的第一個 「TCP 資料包文的序列號和確認號」都是和「第三次握手的 ACK 報文中序列號和確認號」一樣的,並且該 TCP 資料包文也有將 ACK 標記位置為 1。如下圖:

在這裡插入圖片描述在這裡插入圖片描述

所以,伺服器端收到這個資料包文,是可以正常完成連線的建立,然後就可以正常接收這個封包了。

四次揮手階段的變化

最後,我們來看看四次揮手階段中,序列號和確認號的變化。

資料傳輸階段結束後,使用者端發起了 FIN 報文,請求伺服器端端開該 TCP 連線,此時就進入了 TCP 四次揮手階段,如下圖。

在這裡插入圖片描述在這裡插入圖片描述

使用者端傳送的第一次揮手的序列號和確認號分別設定為:

  • 序列號設定為 client_isn + 11。使用者端上一次傳送的報文是 [PSH, ACK] ,該報文的 seq = client_isn + 1, len = 10,根據公式 1(_序列號 = 上一次傳送的序列號 + len_),可以得出當前的序列號為 client_isn + 11。
  • 確認號設定為 server_isn + 1。使用者端上一次收到的報文是伺服器端發來的 ACK 報文,該報文的 seq = server_isn + 1,是單純的 ACK 報文,不攜帶使用者資料,所以 len 為 0。那麼根據公式 2(確認號 = 上一次收到的序列號 + len),可以得出當前的確認號為 server_isn + 1 + 0 (len = 0),也就是 server_isn + 1。

伺服器端傳送的第二次揮手的序列號和確認號分別設定為:

  • 序列號設定為 server_isn + 1。伺服器端上一次傳送的報文是 ACK 報文,該報文的 seq = server_isn + 1,而該報文是單純的 ACK 報文,不攜帶使用者資料,所以 len 為 0,根據公式 1(_序列號 = 上一次傳送的序列號 + len_),可以得出當前的序列號為 server_isn + 1 + 0 (len = 0),也就是 server_isn + 1。
  • 確認號設定為 client_isn + 12。伺服器端上一次收到的報文是使用者端發來的 FIN 報文,該報文的 seq = client_isn + 11,根據公式 2(_確認號= _上一次_收到的序列號 + len,特殊情況,如果收到報文是 SYN 報文或者 FIN 報文,則改為 + 1_),可以得出當前的確認號為 client_isn + 11 + 1,也就是 client_isn + 12。

伺服器端傳送的第三次揮手的序列號和確認號還是和第二次揮手中的序列號和確認號一樣。

  • 序列號設定為 server_isn + 1。
  • 確認號設定為 client_isn + 12。

使用者端傳送的四次揮手的序列號和確認號分別設定為:

  • 序列號設定為 client_isn + 12。使用者端上一次傳送的報文是 FIN 報文,該報文的 seq = client_isn + 11,根據公式 1(_序列號 = 上一次傳送的序列號 + len。特殊情況,如果收到報文是 SYN 報文或者 FIN 報文,則改為 + 1_),可以得出當前的序列號為 client_isn + 11 + 1,也就是 client_isn + 12。
  • 確認號設定為 server_isn + 2。使用者端上一次收到的報文是伺服器端發來的 FIN 報文,該報文的 seq = server_isn + 1,根據公式 2(_確認號 = _上一次_收到的序列號 + len,特殊情況,如果收到報文是 SYN 報文或者 FIN 報文,則改為 + 1_),可以得出當前的確認號為 server_isn + 1 + 1,也就是 server_isn + 2。

實際抓包圖

在這裡貼一個,實際過程中的抓包圖。

在這裡插入圖片描述在這裡插入圖片描述

套入我的萬能公式,傳送的 TCP 報文:

  • 公式一:序列號 = 上一次傳送的序列號 + len(資料長度)。特殊情況,如果上一次傳送的報文是 SYN 報文或者 FIN 報文,則改為 上一次傳送的序列號 + 1。
  • 公式二:確認號 = 上一次收到的報文中的序列號 + len(資料長度)。特殊情況,如果收到的是 SYN 報文或者 FIN 報文,則改為上一次收到的報文中的序列號 + 1。

懂了這套公式之後,相信你在看這類的抓包圖中序列號和確認號的變化的時候,就不會沒有邏輯了。

怎麼樣,學廢了嗎,溜啦溜啦!

更多網路文章

網路基礎篇

HTTP 篇

TCP 篇

IP 篇