源 / 馬哥Linux運(yùn)維
文中很多細(xì)節(jié)就是面試時(shí)關(guān)于“網(wǎng)絡(luò)”這一塊所常問的,還是得多積累一點(diǎn)
上一篇文章 當(dāng)你 ping 的時(shí)候,你知道背后發(fā)生了什么嗎?通過實(shí)際抓包來分析了一次 Ping
的過程(面試常問),我們知道了 ping
是依托于 ICMP
協(xié)議,然后再局域網(wǎng)中還會(huì)涉及到 ARP
請(qǐng)求,今天這篇文章我們同樣用抓包分析工具來分析我們熟悉的 HTTP
請(qǐng)求是怎么樣的?
本來是想找個(gè)網(wǎng)站進(jìn)行抓包分析的,但是正式環(huán)境的網(wǎng)站 HTTP
請(qǐng)求太多,干擾太多,對(duì)分析不太友好,所以簡單些了一個(gè) demo
,對(duì) HTTP
請(qǐng)求返回字符串。
環(huán)境:
1.響應(yīng)http請(qǐng)求的服務(wù)demo
2.客戶端ip:192.168.2.135
3.服務(wù)端:45.76.105.92
4.抓包工具:Wireshark
把 demo
部署到服務(wù)器,啟動(dòng)成功訪問如下:
打開抓包工具 Wireshark
進(jìn)行抓包,抓包結(jié)果如下:
從上圖我們已經(jīng)看到成功抓包到一次 HTTP
請(qǐng)求和響應(yīng)了,但是我們看到卻有很多 TCP
請(qǐng)求,接下來我們來分析下這些 TCP
請(qǐng)求是做什么的?
最開始是本地發(fā)送了2次請(qǐng)求到服務(wù)器,這里為什么會(huì)有兩次請(qǐng)求,稍后再說,我們先主要看 HTTP 對(duì)應(yīng)的端口請(qǐng)求,如下:
192.168.2.135:60738---->45.76.105.92:8081
看上面的截圖我們知道這是 TCP
協(xié)議的第一次握手,熟悉 TCP
協(xié)議的同學(xué)肯定知道 TCP
建立連接有三次握手,斷開連接有四次揮手。
我們先看第一次請(qǐng)求:
60738 -> 8081 [SYN] Seq=0 Win=64240 Len=0 Mss=1460 Ws=256 SACK_PERM=1
我們來解析下這段包請(qǐng)求信息:
60783->8081
端口號(hào):源端口--->目標(biāo)端口
[SYN]
:同步握手信號(hào)
Seq
: 消息編號(hào)
Win
: TCP 窗口大小
Len
: 消息長度
Mss
: 最大報(bào)文段長度
Ws
: 窗口縮放調(diào)整因子
SACK_PERM
: SACK選項(xiàng),這里等于1表示開啟 SACK。
對(duì)于上面的概念,這里簡單解釋下,再介紹之前我們先看 TCPHeader
的數(shù)據(jù)結(jié)構(gòu)圖,對(duì) TCP
頭部數(shù)據(jù)結(jié)構(gòu)有個(gè)直觀的了解
1. Win: TCP 窗口大小,是指 TCP
傳輸能接受的最大字節(jié)數(shù),這個(gè)可以進(jìn)行動(dòng)態(tài)調(diào)節(jié),也就是 TCP
的滑動(dòng)窗口,通過動(dòng)態(tài)調(diào)整窗口大小,來控制發(fā)送數(shù)據(jù)的速率。上圖中占用 2
個(gè)字節(jié),也就是 16
位,那么可以支持的最大數(shù)就是 2^16=65536
,所以默認(rèn)情況下 TCP
頭部標(biāo)記能支持的最大窗口數(shù)是 65536
字節(jié),也就是 64KB
。
2. Len: 消息長度 就是指數(shù)據(jù)報(bào)文段,因?yàn)檎麄€(gè) TCP報(bào)文
= Header
+ packSize
,所以這個(gè)消息長度就是指要傳送的數(shù)據(jù)包總共長度,在本次分析中也就是 HTTP
報(bào)文的大小。
3. Mss: 最大報(bào)文段長度:這個(gè)就是規(guī)定最大的能傳輸報(bào)文的長度,為了達(dá)到最佳的傳輸效能, TCP
協(xié)議在建立連接的時(shí)候通常要協(xié)商雙方的 MSS
值,這個(gè)值 TCP
協(xié)議在實(shí)現(xiàn)的時(shí)候往往用 MTU
值代替(需要減去 IP
數(shù)據(jù)包包頭的大小 20Bytes
和 TCP
數(shù)據(jù)段的包頭 20Bytes
)所以一般 MSS
值 1460
,這也和我們抓包圖中的值一致。
4. Ws: 窗口縮放調(diào)整因子:在前面說 TCP
窗口大小中我們說到,默認(rèn)情況下, TCP
窗口大小最大只能支持 64KB
的緩沖數(shù)據(jù),在今天這個(gè)高速上網(wǎng)時(shí)代,這個(gè)大小肯定不滿足條件了,所以,為了能夠支持更多的緩沖數(shù)據(jù) RFC 1323中就規(guī)定了 TCP
的擴(kuò)展選項(xiàng),其中窗口縮放調(diào)整因子就是其中之一,這個(gè)是如何起作用的呢?首先說明,這個(gè)參數(shù)是在 [SYN]
同步階段進(jìn)行協(xié)商的,我們結(jié)合上面抓包數(shù)據(jù)分析下。我們看到第一次請(qǐng)求協(xié)商的結(jié)果是 WS=256
,然后再 ACK
階段擴(kuò)展因子生效,調(diào)整了窗口大小。生效的抓包如下:
60738 ->8081 [ACK] Seq=1 ACK=1 Win=66560 Len=0
我們發(fā)現(xiàn)這個(gè)窗口變成了 66560
,比默認(rèn)的窗口要大,我們查看報(bào)文詳情:
我們發(fā)現(xiàn),實(shí)際請(qǐng)求聲明的窗口是 260
, WS
擴(kuò)展因子是 256
,最終計(jì)算的窗口大小是 66560
,所以我們知道了,這個(gè)擴(kuò)展因子的作用就是,用原窗口大小乘以擴(kuò)展因子,得到最終的窗口大小,也就是 260*256=66560
.
5. SACK_PERM:SACK選項(xiàng) ,我們知道 TCP
傳輸有包的確認(rèn)機(jī)制,默認(rèn)情況下,接受端接受到一個(gè)包后,發(fā)送 ACK
確認(rèn),但是,默認(rèn)只支持順序的確認(rèn),也就是說,發(fā)送 A
, B
, C
個(gè)包,如果我收到了 A
, C
的包, B
沒有收到,那么對(duì)于 C
,這個(gè)包我是不會(huì)確認(rèn)的,需要等 B
這個(gè)包收到后再確認(rèn),那么 TCP
有超時(shí)重傳機(jī)制,如果一個(gè)包很久沒有確認(rèn),就會(huì)當(dāng)它丟失了,進(jìn)行重傳,這樣會(huì)造成很多多余的包重傳,浪費(fèi)傳輸空間。為了解決這個(gè)問題, SACK
就提出了選擇性確認(rèn)機(jī)制,啟用 SACK
后,接受端會(huì)確認(rèn)所有收到的包,這樣發(fā)送端就只用重傳真正丟失的包了。
簡單介紹了上面的基礎(chǔ)概念后,我們來根據(jù)抓包梳理下 HTTP
請(qǐng)求的過程,根據(jù) HTTP
請(qǐng)求本地端口是 60378
,梳理的流程如下:
------------------------請(qǐng)求連接--------------------------
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
我們根據(jù)上面的流程梳理,可以知道, 序號(hào)1
- 序號(hào)3
是明顯的三次握手,然后 序號(hào)4
進(jìn)行了一次 HTTP
請(qǐng)求,接著 序號(hào)5
是對(duì) HTTP
請(qǐng)求的一次接收確認(rèn), 序號(hào)6
是響應(yīng) HTTP
請(qǐng)求, 序號(hào)7
是對(duì)響應(yīng)請(qǐng)求的確認(rèn)。
上述序號(hào) 8
, 9
, 10
是我關(guān)閉瀏覽器后抓到的包,既然是關(guān)閉瀏覽器,我們肯定知道就是 TCP
連接的斷開了。這里有同學(xué)應(yīng)該已經(jīng)發(fā)現(xiàn)了問題了,我們的斷開是 4
次揮手,你這抓的包只有三條記錄,是你寫錯(cuò)了吧?我要告訴你的是,我沒有寫錯(cuò),這是真實(shí)的抓包抓的,至于為什么是三次,我們來分析一下:
正常情況下,連接斷開是 4
次揮手的, 4
次揮手過程如下圖:
我們分析這圖,揮手流程是這樣的:
1. 客戶端發(fā)起一個(gè)斷開請(qǐng)求,進(jìn)入 FIN-WAIT 狀態(tài)
2. 服務(wù)端確認(rèn)斷開請(qǐng)求
3. 服務(wù)端立即發(fā)送一個(gè)斷開請(qǐng)求,進(jìn)入 CLOSE-WAIT 狀態(tài)
4. 客戶端確認(rèn)服務(wù)端斷開請(qǐng)求,進(jìn)入 TIME-WAIT 狀態(tài)
我們發(fā)現(xiàn)上面的 流程2
和 流程3
都是由服務(wù)端發(fā)起的,那么有沒有可能合并這兩個(gè)請(qǐng)求,一次發(fā)送給客戶端?答案是 可以。在 RFC 2581中的 4.2
節(jié)有提到, ack
可以延遲確認(rèn),只要求保證在 500ms
之內(nèi)保證確認(rèn)包到達(dá)即可。在這樣的標(biāo)準(zhǔn)下, TCP
確認(rèn)是有可能進(jìn)行合并延遲確認(rèn)的,所以,根據(jù)這一點(diǎn),我們推斷下面這個(gè)包:
9) 8081 -> 60738 [FIN ACK] Seq=120 Ack=397 Win=30336 Len=0
合并了對(duì)客戶端的 ack
確認(rèn)以及服務(wù)端發(fā)送的 FIN
斷開信號(hào)包。我們點(diǎn)擊該包詳情如下: 這里紅框中體現(xiàn)了,這個(gè) 9號(hào)包
是對(duì) Frame500
的 ACK
確認(rèn),我們根據(jù)最開始的截圖可以知道,這個(gè)包就是 8號(hào)包
8) 60738 -> 8081 [FIN ACK] Seq=396 Ack=120 Win=66560 Len=0
并且 9號(hào)包
本身自己是發(fā)送的 FIN
信號(hào)包,所以,我們可以認(rèn)為 9號(hào)包
合并了 ACK
和 FIN
的內(nèi)容,所以通常的 4
次揮手,經(jīng)過合并后變成了 3
次揮手。
以上就是一個(gè) HTTP
完整的請(qǐng)求,整個(gè)流程用圖表示如下:
這里肯定有同學(xué)會(huì)問,既然這是一次完整的 HTTP 請(qǐng)求,那么是不是每次請(qǐng)求都會(huì)有三次握手嗎?
答案是:目前的協(xié)議是不用的
在 HTTP0.9
版本和 HTTP1.0
版本中,每次請(qǐng)求響應(yīng)都是要三次握手的, 但是 HTTP1.0
開始嘗試持續(xù)連接,也就是 Keep-Alive
參數(shù),但是官方還沒有正式支持,在 HTTP1.1
協(xié)議中,官方默認(rèn)就是支持 Keep-Alive
參數(shù)的,默認(rèn)是持續(xù)連接。 Keep-Alive
的作用主要有兩點(diǎn):
1.檢查死節(jié)點(diǎn)
2.防止連接由于不活躍而斷開
檢查死節(jié)點(diǎn)
主要是為了讓連接快速失敗被發(fā)現(xiàn),可以進(jìn)行重新連接,比如 A
和 B
兩端已經(jīng)建立了連接, B
節(jié)點(diǎn)因?yàn)?異常原因掛掉了,同時(shí) A
節(jié)點(diǎn)并不知道,這時(shí)候有兩種情況:
1.假設(shè) B
節(jié)點(diǎn)還沒有恢復(fù),那么 B
節(jié)點(diǎn)不會(huì)回復(fù) ACK
, A
節(jié)點(diǎn)就會(huì)一直重試,重試到一定次數(shù)才能知道 B
節(jié)點(diǎn)是死節(jié)點(diǎn)。
2. B
節(jié)點(diǎn)在 A
發(fā)送數(shù)據(jù)之前重啟成功了,這個(gè)時(shí)候 A
節(jié)點(diǎn)發(fā)送數(shù)據(jù), B
節(jié)點(diǎn)并不會(huì)接受,而是會(huì)發(fā)送一個(gè) RST
信號(hào)(在一個(gè)已關(guān)閉的 socket
上收到數(shù)據(jù)時(shí),將發(fā)送 RST
數(shù)據(jù)包,要求對(duì)端關(guān)閉異常連接且對(duì)端不需要回復(fù) ACK
),然后 A
才知道 B
節(jié)點(diǎn)需要重連了。
以上兩種情況,都會(huì)導(dǎo)致只有到發(fā)送數(shù)據(jù)的時(shí)候才知道對(duì)方已經(jīng)出異常了。而 Keep-Alive
每隔一段時(shí)間就會(huì)發(fā)送心跳,就可以很快的知道服務(wù)端節(jié)點(diǎn)的情況。
防止連接由于不活躍而斷開
我們知道,網(wǎng)絡(luò)連接的建立和維持是消耗資源的,一個(gè)服務(wù)器上能建立的連接是有限的,所以像防火墻或者操作系統(tǒng)中會(huì)為了節(jié)省資源會(huì)釋放掉不活躍的連接,而 Keep-Alive
每隔一段時(shí)間發(fā)送一個(gè)心跳包,就是告訴防火墻或者操作系統(tǒng),我這個(gè)連接是活躍的,不要?dú)⑽摇?/span>
后來重新抓了一次帶有 Keep-Alive
的包,截圖如下:
在上圖中最后兩個(gè)包就是發(fā)的 Keep-Alive
包,然后服務(wù)端進(jìn)行 ACK
確認(rèn),我們看到 keep-alive
包,實(shí)際上是會(huì)發(fā)帶有一個(gè)字節(jié)的包,這就是 keep-alive
的實(shí)現(xiàn)。
說完 Keep-Alive
,我們回到最開始的問題,為啥一次 HTTP
請(qǐng)求會(huì)有進(jìn)行兩個(gè)端口的握手呢?其實(shí),這個(gè)和協(xié)議本身沒有任何關(guān)系,第一個(gè)抓包的截圖是用谷歌瀏覽器訪問的,最后一個(gè)抓包圖是用火狐瀏覽器訪問的,仔細(xì)對(duì)比我們發(fā)現(xiàn),火狐瀏覽器只有一個(gè)端口三次握手。所以這種情況的發(fā)生就是瀏覽器自身的實(shí)現(xiàn),谷歌瀏覽器為什么會(huì)這么實(shí)現(xiàn),猜測是:盡可能的保證HTTP訪問的可用性,當(dāng)某個(gè)端口不可用,可以立即切換到另外一個(gè)端口,完成HTTP的請(qǐng)求和響應(yīng)。(個(gè)人猜測,如果有權(quán)威解答,可以評(píng)論區(qū)交流)
HTTP
請(qǐng)求是依托于 TCP
連接的,第一次連接的時(shí)候會(huì)進(jìn)行 TCP
的三次握手。
HTTP
通過 Keep-Alive
來進(jìn)行持久連接,通過定時(shí)發(fā)送一個(gè)心跳包,來告訴服務(wù)端自己還活躍。
HTTP
連接的斷開也會(huì)導(dǎo)致 TCP
的四次揮手,但是如果服務(wù)器判斷滿足條件,會(huì)合并 ACK
和 FIN
信號(hào),進(jìn)而轉(zhuǎn)化為三次揮手。
聯(lián)系客服