抓包分析 TCP 握手和揮手

2022-11-06 18:00:53

前言

首先需要明確的是 TCP 是一個可靠傳輸協定,它的所有特點最終都是為了這個可靠傳輸服務。在網上看到過很多文章講 TCP 連線的三次握手和斷開連線的四次揮手,但是都太過於理論,看完感覺總是似懂非懂。反覆思考過後,覺得我自己還是偏工程型的人,要學習這些理論性的知識,最好的方式還是要通過實際案例來理解,這樣才會具象深刻。本文通過 Wireshark 抓包來分析 TCP 三次握手四次揮手,如果你也對這些理論感覺似懂非懂,那麼強烈建議你也結合抓包實踐來強化理解這些理論性的知識。

三次握手

TCP 建立連線的三次握手是連線的雙方協商確認一些資訊(Sequence number、Maximum Segment Size、Window Size 等),Sequence number 有兩個作用:一個是 SYN 標識位為 1 時作為初始序列號(ISN),則實際第一個資料位元組的序列號和相應 ACK 中的確認號就是這個序列號加 1;另一個是 SYN 標識位為 0 時,則是當前對談的 segment(傳輸層叫 segment,網路層叫 packet,資料鏈路層叫 frame)的第一個資料位元組的累積序列號。Maximum Segment Size 簡稱 MSS,表示最大一個 segment 中能傳輸的資訊(不含 TCP、IP 頭部)。Window Size 表示傳送方接收視窗的大小。下面看看我在本地存取部落格 mghio 的三次握手過程:

圖中三個小紅框表示與伺服器建立連線的三次握手。

  1. 第一步,client 端(這個範例也就是瀏覽器)傳送 SYN 到 server 端;
  2. 第二步,server 端收到 SYN 訊息後,回覆 SYN + ACK 到client 端,ACK 表示已經收到了 client 的 SYN 訊息;
  3. 第三步,client 端收到回覆 SYN + ACK 後,也回覆一個 ACK 表示收到了 server 端的 SYN + ACK 了,其實

到這一步,client 端的 60469 埠已經是 ESTABLISHED 狀態了。
可以看到,其實三次握手的核心目的就是雙方互相告知物件自己的 Sequence number,藍框是 client 端的初始 Sequence number 和 client 端回覆的 ACK,綠框是 server 端的初始 Sequence number 和 client 端回覆的 ACK。這樣協商好初始 Sequence number 後,傳送封包時傳送端就可以判斷丟包和進行丟包重傳了。

三次握手還有一個目的是協商一些資訊(上圖中黃色方框是 Maximum Segment Size,粉色方框是 Window Size)。

到這裡,就可以知道平常所說的建立TCP連線本質是為了實現 TCP 可靠傳輸做的前置準備工作,實際上物理層並沒有這個連線在那裡。TCP 建立連線之後時擁有和維護一些狀態資訊,這個狀態資訊就包含了 Sequence number、MSS、Window Size 等,TCP 握手就是協商出來這些初始值。而這些狀態才是我們平時所說的 TCP 連線的本質。因為這個太重要了,我還要再次強調一下,TCP 是一個可靠傳輸協定,它的所有特點最終都是為了這個可靠傳輸服務

四次揮手

下面再來看看,當關閉瀏覽器頁面是發生斷開連線的四次揮手過程:

相信你已經發現了,上圖抓包抓到的不是四次揮手,而是三次揮手,這是為何呢?

這是由於 TCP 的時延機制(因為系統核心並不知道應用能不能立即關閉),當被揮手端(這裡是 server 的 443 埠)第一次收到揮手端(這裡是 client 的 63612 埠)的 FIN 請求時,並不會立即傳送 ACK,而是會經過一段延遲時間後再傳送,但是此時被揮手端也沒有資料傳送,就會向揮手端傳送 FIN 請求,這裡就可能造成被揮手端傳送的 FIN 與 ACK 一起被揮手端收到,導致出現第二、三次揮手合併為一次的現象,也就最終呈現出「三次揮手」的情況。

斷開連線四次揮手分為如下四步(假設沒有出現揮手合併的情況):

  1. 第一步,client 端主動傳送 FIN 包給 server 端;
  2. 第二步,server 端回覆 ACK(對應第一步 FIN 包的 ACK)給 client,表示 server 知道 client 端要斷開了;
  3. 第三步,server 端傳送 FIN 包給 client 端,表示 server 端也沒有資料要傳送了,可以斷開了;
  4. 第四步,client 端回覆 ACK 包給 server 端,表示既然雙發都已傳送 FIN 包表示可以斷開,那麼就真的斷開了啊。

下面是 TCP 連線流轉狀態圖(其中 CLOSED 狀態是虛擬的,實際上並不存在),這個圖很重要,記住這個圖後基本上所有的 TCP 網路問題就可以解決。

其中比較難以理解的是 TIME_WAIT 狀態,主動關閉的那一端會經歷這個狀態。這一端停留在這個狀態的最長時間是 Maximum segment lifetime(MSL)的 2 倍,大部分時候被簡稱之為 2MSL。存在 TIME_WAIT 狀態有如下兩個原因:

  1. 要可靠的實現 TCP 全雙工連線終止;
  2. 讓老的重複 segment 在網路中消失(一個 sement 在網路中存活的最長時間為 1 個 MSL,一來一回就是 2 MSL);

為什麼握手是三次,而揮手是四次?

嘿嘿,這是個經典的面試題,其實大部分人都背過揮手是四次的原因:因為 TCP 是全雙工(雙向)的,所以回收需要四次......。但是再反問下:握手也是雙向的,但是為什麼是隻要三次呢?

網上流傳的資料都說 TCP 是雙向的,所以回收需要四次,但是握手也是雙向(握手雙方都在告知對方自己的初始 Sequence number),那麼為什麼就不用四次握手呢?所以凡事需要多問幾個為什麼,要有探索和懷疑精神。

你再仔細回看上面三次握手的第二步(SYN + ACK),其實是可以拆分為兩步的:第一步回覆 ACK,第二步再發 SYN 也是完全可以的,只是效率會比較低,這樣的話三次握手不也變成四次握手了。

看起來四次揮手主要是收到第一個 FIN 包後單獨回覆了一個 ACK 包這裡多了一次,如果能像握手那樣也回覆 FIN + ACK 那麼四次揮手也就變成三次了。這裡再貼一下上面這個揮手的抓包圖:

這個圖中第二個紅框就是 server 端回覆的 FIN + ACK 包,這樣四次揮手變成三次了(如果一個包算一次的話)。這裡使用四次揮手原因主要是:被動關閉端在收到 FIN 後,知道主動關閉端要關閉了,然後系統核心層會通知應用層要關閉,此時應用層可能還需要做些關閉前的準備工作,可能還有資料沒傳送完,所以系統核心先回復一個 ACK 包,然後等應用層準備好了主動調 close 關閉時再發 FIN 包。

而握手過程中就沒有這個準備過程了,所以可以立即傳送 SYN + ACK(在這裡的兩步合成一步了,提高效率)。揮手過程中系統核心在收到對方的 FIN 後,只能 ACK,不能主動替應用來 FIN,因為系統核心並不知道應用能不能立即關閉。

總結

TCP 是一個很複雜的協定,為了實現可靠傳輸以及處理各種網路傳輸中的 N 多問題,有一些很經典的解決方案,比如其中的網路擁塞控制演演算法、滑動視窗、資料重傳等。強烈建議你去讀一下 rfc793TCP/IP 詳解 卷1:協定 這本書。

如果你是那些純看理論就能掌握好一門技能,然後還能舉三反一的人,那我很佩服你;如果不是,那麼學習理論知識注意要結合實踐來強化理解理論,要經過反反覆覆才能比較好地掌握一個知識,講究技巧,必要時要學會通過工具來達到目的。

最後 TCP 所有特性基本上核心都是為了實現可靠傳輸這個目標來服務的,然後有一些是出於優化效能的目的。