? 抓包分析 TCP 建立和斷開連線的流程

JS菌發表於2019-03-31

20190331104134.png

⭐️ 更多前端技術和知識點,搜尋訂閱號 JS 菌 訂閱

HTTP 協議是基於 TCP 協議的。大家都知道傳送 HTTP 報文需要首先建立客戶端和服務端之間的 TCP 連線。TCP 三次握手建立連線,四次揮手斷開連線,再熟悉不過。本文實踐一下 TCP 建立和斷開的整個流程,並通過抓包工具進行逐一分析。

開始之前呢,先安裝抓包工具,這裡用的是 Wireshark 正常下載安裝,不再贅述

然後我們還需要 curl 如果在 windows 中沒有這個模組,可以通過 Chocolatey 去安裝,或者用 wget、瀏覽器啥的

兩個準備工作做好了,就可以開始分析工作了。

TCP 建立連線

首先回顧一下 HTTP 請求是怎麼傳送的:

  • 先是建立 TCP 連線
    • 首先,服務端準備接收客戶端請求,狀態變為 LISTEN;客戶端傳送建立連線請求包,攜帶一個 SYNSeq=x;此時客戶端狀態為 SYN_SENT 狀態
    • 服務端收到請求後,同意連線返回一個同意連線的包,攜帶一個 SYN,ACKSeq=yAck=x+1;服務端狀態變為 SYN_RCVD
    • 客戶端收到確認後,還要傳送一個確認的確認連線包,攜帶一個 ACKAck=y+1;服務端收到後,客戶端服務端都為 ESTABLISHED 狀態;連線建立成功
  • 客戶端傳送 HTTP 請求
    • 組裝 HTTP 請求行、請求首部和實體

⚠️ 一定要注意 ACK 和 Ack 是不同的概念,前者是 Acknowledgement 確認值,後者是 Acknowledgement Number 確認編號

開始抓包:

開啟 Wireshark,左上角鯊魚鰭標誌,然後在終端中使用 curl 給傳送一個 GET 請求,這裡以 httpbin.org/json 為例

20190331223151.png

回到 Wireshark,在過濾器中輸入 http,只檢視 http 應用層的資訊:

20190331223300.png

然後我們選擇明顯是 /json 網址的記錄,右鍵選擇 follow 子選單中的 HTTP Stream:

20190331223430.png

彈出的視窗是 HTTP 請求報文,先關閉視窗暫時用不到這些東西

20190331121320.png

此時皮膚中就是整個 TCP 建立、傳送 HTTP 請求並獲取響應以及斷開 TCP 連線的過程

客戶端傳送請求建立連線

第一條記錄顯示了我的電腦埠傳送了一個 TCP 連線的包,這個包攜帶了一個 SYN flag,Seq 被設定為 0;這就是請求建立 TCP 連線的包

20190331224046.png

所以客戶端請求建立 TCP 連線時是傳送 SYN 的包,其中 Seq 被設定為 0(實際上有可能不為 0)

服務端返回確認資訊

第二條是第一條包的確認資訊:

20190331224337.png

看到這是一個確認包,這裡的 flag 是 SYN 和 ACK,其中 Seq 為新設定的值為 0( ⚠️ 注意這裡的 Seq 與此前傳送的 Seq 不是一個值)

另外確認序號 Ack 是之前為 0 的,接收到的那個序號 Seq + 1,值為 1

客戶端傳送確認資訊

第三條就是第二條包的確認資訊,表示確認收到服務端的確認資訊

20190331224714.png

第三個包可以看到有一個 ACK,同時序號 Seq 為第一次傳送請求建立連線時候的 Seq + 1,值為 1( ⚠️ 注意這裡的 Seq 與服務端返回的 Seq 不是一個值),Ack 確認序號就是收到的服務端傳送的包 Seq + 1,值為 1

至此 TCP 連線成功

20190331201124.png

⚠️ 一定要注意區別開雙方傳送的 Seq 不是一個東西,Ack 是確認收到對方的包,在對方傳送的這個包的 Seq 基礎上增加 1。自己傳送接下來的包,則是在自己傳送的上一個包的 Seq 基礎上增加 1;另外還要區別 Ack 和 ACK 是不同的;

TCP 斷開連線

客戶端主動斷開 TCP 連線的過程如下:

  • 客戶端傳送斷開連線的請求包,攜帶一個 FIN, ACKSeq=xAck=y;此時客戶端狀態為 FIN_WAIT_1
  • 服務端同意斷開連線,返回一個 ACKAck=x+1;服務端可能還有資料需要傳送,繼續傳送並將狀態變為 CLOSE_WAIT 狀態;客戶端收到並將狀態變為 FIN_WAIT_2;繼續接收資料。
  • 資料傳輸完畢,服務端傳送一個 FINSeq=z+1(這裡的 z 是最後一次服務端傳送的 Seq 序號);服務端狀態變為 LAST_ACK;客戶端收到並將狀態變為 TIME_WAIT
  • 資料接收到之後,客戶端傳送一個 ACK,這裡的 Ack=z+2(就是最後一次接收到的序號 Seq 加一)

Wireshark 抓包記錄繼續分析:

20190331232257.png

首先客戶端傳送一個 FIN, ACK,切序號 Seq 為 80,Ack 為 650,請求斷開連線

20190331232646.png

服務端返回一個 ACK 和一個 FIN,因為沒有更多資料傳輸,所以原本兩個資料包被合併成一個,因此這裡四次揮手因合併而變為“三次揮手”

這裡的 Seq 為 650,確認序號 Ack 為收到序號加一也就是 80 + 1 = 81

20190331233025.png

最後客戶端傳送一個 ACK,就代表 TCP 連線正式斷開,Ack 為收到序號加一也就是 650 + 1 = 651

整個 TCP 通訊過程就是這樣

⚠️ Seq 序號和 Ack 確認序號比較亂;這裡提個醒 Ack 始終為最後收到包的序號 Seq + 1;而 Seq 則是上一個傳送出去的包的 Seq + 1

有哪裡有講的不準確的地方也請指正謝謝

JS 菌公眾賬號

請關注我的訂閱號,不定期推送有關 JS 的技術文章,只談技術不談八卦 ?

相關文章