抓包分析 TCP 握手和揮手

前言

首先需要明確的是 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 的三次握手過程:

three-way-hand-shake.jpg

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

  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)。

three-way-hand-shake-dg.jpg

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

四次揮手

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

tcp-close-sequence.jpg

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

這是由於 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 網絡問題就可以解決。

tcp_state_diagram.png

其中比較難以理解的是 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 那麼四次揮手也就變成三次了。這裏再貼一下上面這個揮手的抓包圖:

tcp-close-sequence.jpg

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

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

總結

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

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

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

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