天天看點

TCP 序列号和确認号是如何變化的?

大家好,我是小林。

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

也有很多同學跟我回報,希望我寫一篇關于 TCP 序列号和确認号變化過程的文章。大家别小看這個基礎知識點,其實很多人都不知道的。

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

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

萬能公式

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

發送的 TCP 封包:

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

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

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

TCP 序列号和确認号是如何變化的?

這裡重點關注這三個字段的作用:

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

三次握手階段的變化

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

假設用戶端的初始化序列号為 client_isn,服務端的初始化序列号為 server_isn,TCP 三次握手的流程如下:

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 位元組的資料,流程圖下:

TCP 序列号和确認号是如何變化的?

用戶端發送 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。如下圖:

TCP 序列号和确認号是如何變化的?

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

四次揮手階段的變化

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

資料傳輸階段結束後,用戶端發起了 FIN 封包,請求服務端端開該 TCP 連接配接,此時就進入了 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 序列号和确認号是如何變化的?

套入我的萬能公式,發送的 TCP 封包:

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

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

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

更多網絡文章

網絡基礎篇

  • ​​TCP/IP 網絡模型有哪幾層?​​
  • ​​鍵入網址到網頁顯示,期間發生了什麼?​​
  • ​​Linux 系統是如何收發網絡包的?​​

HTTP 篇

  • ​​HTTP 常見面試題​​
  • ​​HTTP/1.1如何優化?​​
  • ​​HTTPS RSA 握手解析​​
  • ​​HTTPS ECDHE 握手解析​​
  • ​​HTTPS 如何優化?​​
  • ​​HTTP/2 牛逼在哪?​​
  • ​​HTTP/3 強勢來襲​​
  • ​​既然有 HTTP 協定,為什麼還要有 RPC?​​
  • ​​TCP 三次握手與四次揮手面試題​​
  • ​​TCP 重傳、滑動視窗、流量控制、擁塞控制​​
  • ​​TCP 實戰抓包分析​​
  • ​​TCP 半連接配接隊列和全連接配接隊列​​
  • ​​如何優化 TCP?​​
  • ​​如何了解是 TCP 面向位元組流協定?​​
  • ​​為什麼 TCP 每次建立連接配接時,初始化序列号都要不一樣呢?​​
  • ​​SYN 封包什麼時候情況下會被丢棄?​​
  • ​​四次揮手中收到亂序的 FIN 包會如何處理?​​
  • ​​在 TIME_WAIT 狀态的 TCP 連接配接,收到 SYN 後會發生什麼?​​
  • ​​TCP 連接配接,一端斷電和程序崩潰有什麼差別?​​
  • ​​拔掉網線後, 原本的 TCP 連接配接還存在嗎?​​
  • ​​tcp_tw_reuse 為什麼預設是關閉的?​​
  • ​​HTTPS 中 TLS 和 TCP 能同時握手嗎?​​
  • ​​TCP Keepalive 和 HTTP Keep-Alive 是一個東西嗎?​​
  • ​​TCP 有什麼缺陷?​​
  • ​​如何基于 UDP 協定實作可靠傳輸?​​
  • ​​TCP 和 UDP 可以使用同一個端口嗎?​​
  • ​​服務端沒有 listen,用戶端發起連接配接建立,會發生什麼?​​
  • ​​沒有 accpet,可以建立 TCP 連接配接嗎?​​
  • ​​用了 TCP 協定,資料一定不會丢嗎?​​
  • ​​IP 基礎知識全家桶​​
  • ​​ping 的工作原理​​

繼續閱讀