大家好,我是小林。
在網站上回答了很多人的問題,我發現很多人對 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 篇
- HTTP 常見面試題
- HTTP/1.1如何優化?
- HTTPS RSA 握手解析
- HTTPS ECDHE 握手解析
- HTTPS 如何優化?
- HTTP/2 牛逼在哪?
- HTTP/3 強勢來襲
- 既然有 HTTP 協議,爲什麼還要有 RPC?
TCP 篇
- 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 篇