⭐️ 更多前端技術和知識點,搜尋訂閱號
JS 菌
訂閱
HTTP 協議是基於 TCP 協議的。大家都知道傳送 HTTP 報文需要首先建立客戶端和服務端之間的 TCP 連線。TCP 三次握手建立連線,四次揮手斷開連線,再熟悉不過。本文實踐一下 TCP 建立和斷開的整個流程,並通過抓包工具進行逐一分析。
開始之前呢,先安裝抓包工具,這裡用的是 Wireshark
正常下載安裝,不再贅述
然後我們還需要 curl
如果在 windows 中沒有這個模組,可以通過 Chocolatey
去安裝,或者用 wget
、瀏覽器啥的
兩個準備工作做好了,就可以開始分析工作了。
TCP 建立連線
首先回顧一下 HTTP 請求是怎麼傳送的:
- 先是建立 TCP 連線
- 首先,服務端準備接收客戶端請求,狀態變為
LISTEN
;客戶端傳送建立連線請求包,攜帶一個SYN
,Seq=x
;此時客戶端狀態為SYN_SENT
狀態 - 服務端收到請求後,同意連線返回一個同意連線的包,攜帶一個
SYN,ACK
,Seq=y
,Ack=x+1
;服務端狀態變為SYN_RCVD
- 客戶端收到確認後,還要傳送一個確認的確認連線包,攜帶一個
ACK
,Ack=y+1
;服務端收到後,客戶端服務端都為ESTABLISHED
狀態;連線建立成功
- 首先,服務端準備接收客戶端請求,狀態變為
- 客戶端傳送 HTTP 請求
- 組裝 HTTP 請求行、請求首部和實體
⚠️ 一定要注意 ACK 和 Ack 是不同的概念,前者是 Acknowledgement
確認值,後者是 Acknowledgement Number
確認編號
開始抓包:
開啟 Wireshark,左上角鯊魚鰭標誌,然後在終端中使用 curl 給傳送一個 GET 請求,這裡以 httpbin.org/json 為例
回到 Wireshark,在過濾器中輸入 http,只檢視 http 應用層的資訊:
然後我們選擇明顯是 /json
網址的記錄,右鍵選擇 follow 子選單中的 HTTP Stream:
彈出的視窗是 HTTP 請求報文,先關閉視窗暫時用不到這些東西
此時皮膚中就是整個 TCP 建立、傳送 HTTP 請求並獲取響應以及斷開 TCP 連線的過程
客戶端傳送請求建立連線
第一條記錄顯示了我的電腦埠傳送了一個 TCP 連線的包,這個包攜帶了一個 SYN flag,Seq 被設定為 0;這就是請求建立 TCP 連線的包
所以客戶端請求建立 TCP 連線時是傳送 SYN 的包,其中 Seq 被設定為 0(實際上有可能不為 0)
服務端返回確認資訊
第二條是第一條包的確認資訊:
看到這是一個確認包,這裡的 flag 是 SYN 和 ACK,其中 Seq 為新設定的值為 0( ⚠️ 注意這裡的 Seq 與此前傳送的 Seq 不是一個值)
另外確認序號 Ack 是之前為 0 的,接收到的那個序號 Seq + 1,值為 1
客戶端傳送確認資訊
第三條就是第二條包的確認資訊,表示確認收到服務端的確認資訊
第三個包可以看到有一個 ACK,同時序號 Seq 為第一次傳送請求建立連線時候的 Seq + 1,值為 1( ⚠️ 注意這裡的 Seq 與服務端返回的 Seq 不是一個值),Ack 確認序號就是收到的服務端傳送的包 Seq + 1,值為 1
至此 TCP 連線成功
⚠️ 一定要注意區別開雙方傳送的 Seq 不是一個東西,Ack 是確認收到對方的包,在對方傳送的這個包的 Seq 基礎上增加 1。自己傳送接下來的包,則是在自己傳送的上一個包的 Seq 基礎上增加 1;另外還要區別 Ack 和 ACK 是不同的;
TCP 斷開連線
客戶端主動斷開 TCP 連線的過程如下:
- 客戶端傳送斷開連線的請求包,攜帶一個
FIN, ACK
,Seq=x
,Ack=y
;此時客戶端狀態為FIN_WAIT_1
- 服務端同意斷開連線,返回一個
ACK
,Ack=x+1
;服務端可能還有資料需要傳送,繼續傳送並將狀態變為CLOSE_WAIT
狀態;客戶端收到並將狀態變為FIN_WAIT_2
;繼續接收資料。 - 資料傳輸完畢,服務端傳送一個
FIN
,Seq=z+1
(這裡的 z 是最後一次服務端傳送的 Seq 序號);服務端狀態變為LAST_ACK
;客戶端收到並將狀態變為TIME_WAIT
- 資料接收到之後,客戶端傳送一個
ACK
,這裡的Ack=z+2
(就是最後一次接收到的序號 Seq 加一)
Wireshark 抓包記錄繼續分析:
首先客戶端傳送一個 FIN, ACK,切序號 Seq 為 80,Ack 為 650,請求斷開連線
服務端返回一個 ACK 和一個 FIN,因為沒有更多資料傳輸,所以原本兩個資料包被合併成一個,因此這裡四次揮手因合併而變為“三次揮手”
這裡的 Seq 為 650,確認序號 Ack 為收到序號加一也就是 80 + 1 = 81
最後客戶端傳送一個 ACK,就代表 TCP 連線正式斷開,Ack 為收到序號加一也就是 650 + 1 = 651
整個 TCP 通訊過程就是這樣
⚠️ Seq 序號和 Ack 確認序號比較亂;這裡提個醒 Ack 始終為最後收到包的序號 Seq + 1;而 Seq 則是上一個傳送出去的包的 Seq + 1
有哪裡有講的不準確的地方也請指正謝謝
請關注我的訂閱號,不定期推送有關 JS 的技術文章,只談技術不談八卦 ?