上一篇文章《對於Ping的過程,你真的瞭解嗎?》咱們經過抓包工具來分析了一次 Ping 的過程,咱們知道了 ping 是依託於 ICMP 協議,而後再局域網中還會涉及到 ARP 請求,今天這篇文章咱們一樣用抓包分析工具來分析咱們熟悉的 HTTP 請求是怎麼樣的?html
原本我是想找個網站進行抓包分析的,可是正式環境的網站 HTTP 請求太多,干擾太多,對分析不太友好,因此我簡單些了一個demo,對 HTTP 請求返回字符串。瀏覽器
環境:服務器
1.響應http請求的服務demo
2.客戶端ip:192.168.2.135
3.服務端:45.76.105.92
4.抓包工具:Wireshark
複製代碼
我把demo部署到服務器,啓動成功訪問以下:網絡
打開抓包工具 Wireshark 進行抓包,抓包結果以下:數據結構
1.最開始是本地發送了2次請求到服務器,這裏爲何會有兩次請求,稍後再說,咱們先主要看 HTTP 對應的端口請求,以下:socket
192.168.2.135:60738---->45.76.105.92:8081
複製代碼
看上面的截圖咱們知道這是 TCP 協議的第一次握手,熟悉 TCP 協議的同窗確定知道 TCP 創建鏈接有三次握手,斷開鏈接有四次揮手。(對 TCP 協議不太瞭解的同窗能夠查看這篇文章《跟着動畫來學習 TCP 三次握手和四次揮手》)工具
咱們先看第一次請求:post
60738 -> 8081 [SYN] Seq=0 Win=64240 Len=0 Mss=1460 Ws=256 SACK_PERM=1
複製代碼
咱們來解析下這段包請求信息:學習
對於上面的概念,這裏簡單解釋下,再介紹以前咱們先看 TCP Header 的數據結構圖,對 TCP 頭部數據結構有個直觀的瞭解動畫
1. Win: TCP 窗口大小,是指TCP傳輸能接受的最大字節數,這個能夠進行動態調節,也就是TCP的滑動窗口,經過動態調整窗口大小,來控制發送數據的速率。上圖中佔用2個字節,也就是16位,那麼能夠支持的最大數就是2^16=65536,因此默認狀況下TCP頭部標記能支持的最大窗口數是65536字節,也就是64KB。
2. Len: 消息長度 就是指數據報文段,由於整個TCP報文=Header+packSize,因此這個消息長度就是指要傳送的數據包總共長度,在本次分析中也就是HTTP報文的大小。
3. Mss: 最大報文段長度:這個就是規定最大的能傳輸報文的長度,爲了達到最佳的傳輸效能,TCP 協議在創建鏈接的時候一般要協商雙方的 MSS 值,這個值 TCP 協議在實現的時候每每用 MTU 值代替(須要減去IP數據包包頭的大小20Bytes和TCP數據段的包頭20Bytes)因此通常 MSS 值1460,這也和咱們抓包圖中的值一致。
4. Ws: 窗口縮放調整因子:在前面說 TCP 窗口大小中咱們說到,默認狀況下,TCP 窗口大小最大隻能支持64KB的緩衝數據,在今天這個高速上網時代,這個大小確定不知足條件了,因此,爲了可以支持更多的緩衝數據 RFC 1323 中就規定了 TCP 的擴展選項,其中窗口縮放調整因子就是其中之一,這個是如何起做用的呢?首先說明,這個參數是在 [SYN] 同步階段進行協商的,咱們結合上面抓包數據分析下。咱們看到第一次請求協商的結果是WS=256,而後再 ACK 階段擴展因子生效,調整了窗口大小。生效的抓包以下:
60738 ->8081 [ACK] Seq=1 ACK=1 Win=66560 Len=0
複製代碼
咱們發現這個窗口變成了66560,比默認的窗口要大,咱們查看報文詳情:
咱們發現,實際請求聲明的窗口是260,WS擴展因子是256,最終計算的窗口大小是66560,因此咱們知道了,這個擴展因子的做用就是,用原窗口大小乘以擴展因子,獲得最終的窗口大小,也就是260*256=66560.
5. SACK_PERM:SACK選項 ,咱們知道 TCP 傳輸有包的確認機制,默認狀況下,接受端接受到一個包後,發送 ACK 確認,可是,默認只支持順序的確認,也就是說,發送 A,B,C 個包,若是我收到了A,C的包,B沒有收到,那麼對於C,這個包我是不會確認的,須要等B這個包收到後再確認,那麼TCP有超時重傳機制,若是一個包好久沒有確認,就會當它丟失了,進行重傳,這樣會形成不少多餘的包重傳,浪費傳輸空間。爲了解決這個問題,SACK就提出了選擇性確認機制,啓用 SACK 後,接受端會確認全部收到的包,這樣發送端就只用重傳真正丟失的包了。
簡單介紹了上面的基礎概念後,咱們來根據抓包梳理下 HTTP 請求的過程,根據 HTTP 請求本地端口是 60378,我梳理的流程以下:
------------------------請求鏈接--------------------------
1) 60738 -> 8081 [SYN] Seq=0 Win=64240 Len=0 Mss=1460 Ws=256 SACK_PERM=1
2) 8081 -> 60738 [SYN,ACK] Seq=0 ACK =1 Win=29200 Len=0 MSS=1420 SACK_PERM=1 WS=128
3) 60738 -> 8081 [ACK] Seq=1 ACK=1 Win=66560 Len=0
4) Get /test HTTP/1.1
5) 8081 -> 60738 [ACK] Seq=1 ACK=396 Win=30336 Len=0
6) HTTP/1.1 200 (text/html)
7) 60738 -> 8081 [ACK] Seq=396 ACK=120 Win=66560 Len=0
------------------斷開鏈接-----------------------------
8) 60738 -> 8081 [FIN ACK] Seq=396 Ack=120 Win=66560 Len=0
9) 8081 -> 60738 [FIN ACK] Seq=120 Ack=397 Win=30336 Len=0
10) 60738 -> 8081 [ACK] Seq=397 Ack=121 Win=66560 Len=0
複製代碼
咱們根據上面的流程梳理,能夠知道,序號1-序號3是明顯的三次握手,而後序號4進行了一次 HTTP 請求,接着序號5是對 HTTP 請求的一次接收確認,序號6是響應 HTTP 請求,序號7是對響應請求的確認。
上述序號 8,9,10 是我關閉瀏覽器後抓到的包,既然是關閉瀏覽器,咱們確定知道就是 TCP 鏈接的斷開了。這裏有同窗應該已經發現了問題了,咱們的斷開是4次揮手,你這抓的包只有三條記錄,是你寫錯了吧?我要告訴你的是,我沒有寫錯,這是真實的抓包抓的,至於爲何是三次,咱們來分析一下:
正常狀況下,鏈接斷開是4次揮手的,4次揮手過程以下圖:
咱們分析這圖,揮手流程是這樣的:
1.客戶端發起一個斷開請求,進入 FIN-WAIT 狀態
2.服務端確認斷開請求
3.服務端當即發送一個斷開請求,進入 CLOSE-WAIT 狀態
4.客戶端確認服務端斷開請求,進入 TIME-WAIT 狀態
複製代碼
咱們發現上面的流程2和流程3都是由服務端發起的,那麼有沒有可能合併這兩個請求,一次發送給客戶端?答案是 能夠。在 RFC 2581中的4.2 節有提到,ack能夠延遲確認,只要求保證在500ms以內保證確認包到達便可。在這樣的標準下,TCP確認是有可能進行合併延遲確認的,因此,根據這一點,咱們推斷下面這個包:
9) 8081 -> 60738 [FIN ACK] Seq=120 Ack=397 Win=30336 Len=0
複製代碼
合併了對客戶端的ack確認以及服務端發送的FIN斷開信號包。咱們點擊該包詳情以下: 這裏紅框中體現了,這個9號包是對 Frame 500 的 ACK 確認,咱們根據最開始的截圖能夠知道,這個包就是8號包
8) 60738 -> 8081 [FIN ACK] Seq=396 Ack=120 Win=66560 Len=0
複製代碼
而且 9號包 自己本身是發送的 FIN 信號包,因此,咱們能夠認爲 9號包合併了ACK 和 FIN 的內容,因此一般的4次揮手,通過合併後變成了3次揮手。
以上就是一個 HTTP 完整的請求,整個流程用圖表示以下:
這裏確定有同窗會問,既然這是一次完整的 HTTP 請求,那麼是否是每次請求都會有三次握手嗎?
答案是:目前的協議是不用的
在 HTTP 0.9 版本和 HTTP 1.0 版本中,每次請求響應都是要三次握手的, 可是 HTTP 1.0 開始嘗試持續鏈接,也就是 Keep-Alive 參數,可是官方尚未正式支持,在 HTTP 1.1協議中,官方默認就是支持 Keep-Alive 參數的,默認是持續鏈接。Keep-Alive 的做用主要有兩點:
1.檢查死節點
2.防止鏈接因爲不活躍而斷開
複製代碼
檢查死節點
主要是爲了讓鏈接快速失敗被發現,能夠進行從新鏈接,好比A 和 B 兩端已經創建了鏈接,B節點由於 異常緣由掛掉了,同時 A 節點並不知道,這時候有兩種狀況:
1.假設 B 節點尚未恢復,那麼 B 節點不會回覆 ACK,A節點就會一直重試,重試到必定次數才能知道 B 節點是死節點。
2.B節點在A發送數據以前重啓成功了,這個時候A節點發送數據,B節點並不會接受,而是會發送一個 RST 信號(在一個已關閉的 socket 上收到數據時,將發送RST數據包,要求對端關閉異常鏈接且對端不須要回復ACK),而後 A 才知道 B 節點須要重連了。
以上兩種狀況,都會致使只有到發送數據的時候才知道對方已經出異常了。而Keep-Alive 每隔一段時間就會發送心跳,就能夠很快的知道服務端節點的狀況。
防止鏈接因爲不活躍而斷開
咱們知道,網絡鏈接的創建和維持是消耗資源的,一個服務器上能創建的鏈接是有限的,因此像防火牆或者操做系統中會爲了節省資源會釋放掉不活躍的鏈接,而 Keep-Alive 每隔一段時間發送一個心跳包,就是告訴防火牆或者操做系統,我這個鏈接是活躍的,不要殺我。
我從新抓了一次帶有 Keep-Alive 的包,截圖以下:
說完 Keep-Alive,咱們回到最開始的問題,爲啥一次 HTTP 請求會有進行兩個端口的握手呢?其實,這個和協議自己沒有任何關係,第一個抓包的截圖(圖 Http-Request )是我用谷歌瀏覽器訪問的,最後一個抓包圖(圖Keep-Alive)是我用火狐瀏覽器訪問的,仔細對比咱們發現,火狐瀏覽器只有一個端口三次握手。因此這種狀況的發生就是瀏覽器自身的實現,谷歌瀏覽器爲何會這麼實現,個人猜想是:儘量的保證HTTP訪問的可用性,當某個端口不可用,能夠當即切換到另一個端口,完成HTTP的請求和響應。(我的猜想,若是有權威解答,麻煩告知交流)
[1] RFC 1323
[2] RFC 2581