HTTP協(xié)議是“看不著明场、摸不著”的。因為在Java語言中午笛,有太多的HTTP相關(guān)的庫惭蟋,如:HTTPClient、HTTPUrlConnection药磺、Spring的RestTemplete等告组。因此在某種程度上從事Java相關(guān)的開發(fā)人員很難需要去深扒HTTP協(xié)議,而基本上都屬于會用就好的狀態(tài)癌佩。
編寫本文的思路正如下面的文章目錄木缝。
目錄
- 一、HTTP簡介
- 二围辙、什么是協(xié)議
- 2.1 協(xié)議的定義
- 三我碟、Wireshark網(wǎng)絡(luò)抓包工具
- 四、HTTP協(xié)議
- 4.1 前置知識
- 4.2 請求協(xié)議結(jié)構(gòu)
- 4.2.1 描述
- 4.2.2 抓包查看
- 4.3 響應(yīng)協(xié)議結(jié)構(gòu)
- 4.3.1 描述
- 4.3.2 抓包查看
- 4.4 分包傳輸
- 4.4.1 結(jié)構(gòu)圖
- 4.4.2 優(yōu)點(diǎn)
- 4.4.3 缺點(diǎn)
- 4.4.4 抓包查看
- 4.5 KeepAlive
- 4.5.1 Tomcat對KeepAlive的支持
- 4.5.2 TCP協(xié)議與HTTP協(xié)議中的KeepAlive區(qū)別
- 4.5.3 如何復(fù)用長連接
- 4.5.4 如何關(guān)閉長鏈接
- 4.5.5 疑問待解決
- 4.5.6 4.4 分包傳輸
- 五姚建、HTTP協(xié)議如何解決TCP的粘包與拆包
一怎囚、HTTP簡介
HTTP中文名稱是超文本傳輸協(xié)議(英文:HyperText Transfer Protocol),HTTP是基于 TCP/IP 協(xié)議之上的應(yīng)用層協(xié)議桥胞,HTTP是萬維網(wǎng)數(shù)據(jù)通信的基礎(chǔ)。
因此考婴,在HTTP建立之前贩虾,需要先等客戶端與服務(wù)端建立TCP連接。
二沥阱、什么是協(xié)議
在了解具體的HTTP協(xié)議之前缎罢,最好還是先了解什么叫協(xié)議,這對之后的學(xué)習(xí)其它協(xié)議肯定會有幫助。
2.1 協(xié)議的定義
協(xié)議策精,網(wǎng)絡(luò)協(xié)議的簡稱舰始,網(wǎng)絡(luò)協(xié)議是通信計算機(jī)雙方必須共同遵從的一組約定。如怎么樣建立連接咽袜、怎么樣互相識別等丸卷。只有遵守這個約定,計算機(jī)之間才能相互通信交流询刹。它的三要素是:語法谜嫉、語義、時序凹联。
為了使數(shù)據(jù)在網(wǎng)絡(luò)上從源到達(dá)目的沐兰,網(wǎng)路通信的參與方必須遵循相同的規(guī)則,這套規(guī)則稱為協(xié)議(protocol)蔽挠,它最終體現(xiàn)在網(wǎng)絡(luò)上傳輸?shù)臄?shù)據(jù)包的格式住闯。
協(xié)議往往分為幾個層次進(jìn)行定義,分層定義是為了使某一層協(xié)議的改變不影響其他層次的協(xié)議澳淑。
備注:以上文字來源于百度百科
三比原、Wireshark網(wǎng)絡(luò)抓包工具
Wireshark是一個網(wǎng)絡(luò)封包分析軟件。網(wǎng)絡(luò)封包分析軟件的功能是擷取網(wǎng)絡(luò)封包偶惠,并盡可能顯示出最為詳細(xì)的網(wǎng)絡(luò)封包資料春寿。Wireshark使用WinPCAP作為接口,直接與網(wǎng)卡進(jìn)行數(shù)據(jù)報文交換忽孽。
在學(xué)習(xí)HTTP協(xié)議的過程中绑改,因為HTTP協(xié)議的是看不見摸不著的,這給學(xué)習(xí)帶來一定的難度兄一。因此有必要用Wireshark抓包工具抓取一個HTTP協(xié)議下來觀察厘线。
Wireshark官網(wǎng)地址:https://wireshark.en.softonic.com/
先看一下Wireshark的界面:
顯而易見:Wireshark抓包界面中分為頂部的菜單欄、過濾欄出革、上造壮、中、下五個部分骂束;
- 過濾欄:用于寫一些過濾條件耳璧,避免被抓的包太多展箱,導(dǎo)致眼花繚亂不方便觀察包
- 上:請求列表旨枯,包含TCP、HTTP等等協(xié)議包
- 中:一共有5個Item混驰。從上到下分別為:物理層攀隔、數(shù)據(jù)鏈路層皂贩、網(wǎng)絡(luò)層、傳輸層昆汹、應(yīng)用層明刷。有時候還會多一個Item,多的Item只是Wireshark將應(yīng)用層中攜帶的數(shù)據(jù)解析出來展示而已满粗,網(wǎng)絡(luò)包中實際沒有辈末。因此這一區(qū)域的數(shù)據(jù),Wireshark主要是用來展示更加人性化败潦、經(jīng)過加工后的數(shù)據(jù)本冲。
- 下:十六進(jìn)制數(shù)據(jù),是真正網(wǎng)絡(luò)包中的數(shù)據(jù)劫扒;
四檬洞、HTTP協(xié)議
4.1 前置知識
在HTTP協(xié)議中,有很多的符號如:\n\r等沟饥,而也正是因為這些符號對HTTP協(xié)議解決拆包添怔、粘包提供了基礎(chǔ)
標(biāo)識 | ASCII | 描述 | 字符 |
---|---|---|---|
CR | 13 | 回車 | \n |
LF | 10 | 換行 | \r |
SP | 32 | 空格 | |
COLON | 58 | 冒號 | : |
4.2 請求協(xié)議結(jié)構(gòu)
4.2.1 描述
HTTP請求體中包含三個部分:① 請求行、② 請求頭(Header)贤旷、③ 請求正文(Body)
概括為文字如下:
-
請求行:
包含三個部分:Method广料、URL、協(xié)議/版本幼驶,而這之前用空格隔開艾杏,在請求行的最后添加CRLF
-
請求頭:
包含若干個鍵值對(格式為:key:value),在每個鍵值對之間都用CRLF隔開盅藻,在整個請求頭的最后再加上一個CRLF购桑;也就是說最后一個鍵值對的最后會有兩個CRLF。
-
請求正文:
請求正文中主要是POST Method提交的數(shù)據(jù)氏淑,數(shù)據(jù)的格式可以有很多種勃蜘,具體什么格式實在請求頭中由Content-Type定義。在不考慮分包傳輸?shù)那闆r下假残,請求正文中數(shù)據(jù)的字節(jié)數(shù)由Content-Length指定缭贡。
4.2.2 抓包查看
4.3 響應(yīng)協(xié)議結(jié)構(gòu)
4.3.1 描述
HTTP響應(yīng)體中包含三個部分:① 狀態(tài)行、② 響應(yīng)頭(Header)辉懒、③ 響應(yīng)正文(Body)
概括為文字如下:
-
響應(yīng)行:
包含三個部分:協(xié)議/版本阳惹、響應(yīng)狀態(tài)碼、狀態(tài)碼描述符眶俩,而這之前用空格隔開穆端,在請求行的最后添加CRLF
-
響應(yīng)頭:
不說了,響應(yīng)頭格式同Request中的請求頭格式
-
響應(yīng)正文:
不說了仿便,響應(yīng)正文同Request中的請求正文
4.3.2 抓包查看
看到這里体啰,有的小伙伴可能會提出問題:
問:在上圖中[響應(yīng)頭]與[響應(yīng)正文]之間是什么數(shù)據(jù)呢?
答:這些數(shù)據(jù)是Wireshark為了輔助展示出的一些指標(biāo)數(shù)據(jù)嗽仪。實際上荒勇,在網(wǎng)絡(luò)包中并沒有這些數(shù)據(jù),這里對比一下最下面的方塊闻坚,看一下網(wǎng)絡(luò)包中真正的數(shù)據(jù)沽翔。
問:在網(wǎng)絡(luò)分層中,Hypertext Transfer Protocol已經(jīng)是網(wǎng)絡(luò)模型中的應(yīng)用層了窿凤,那為什么上圖的抓包中仅偎,最下面還有一層:Line-base text data:text/plain(1 lines)?
答:這個問題的答案其實和上一個問題的答案一致雳殊,這只是Wireshark為了展示的更加人性化橘沥,將數(shù)據(jù)抽離在了該層展示低而已,注意看最下面的紅色框框夯秃,在Header結(jié)束之后就是兩個CRLF座咆,緊接著就是數(shù)據(jù)(hello world!)了仓洼。
4.4 分包傳輸
在HTTP協(xié)議中介陶,除了將數(shù)據(jù)放在一個包中一次性發(fā)出,還可以通過分包方式將一個大批數(shù)據(jù)分在多個數(shù)據(jù)包中進(jìn)行傳輸色建。需要注意的是哺呜,分包傳輸使用的也是同一個Socket連接。
在Header中通過Transfer-Encoding: chunked進(jìn)行指定箕戳,該KV與Content-Length只能同時出現(xiàn)一個某残。
需要注意的是:在HTTP1.0版本協(xié)議中不支持分包。在HTTP1.1之后開始支持分包漂羊。
4.4.1 結(jié)構(gòu)圖
4.4.2 優(yōu)點(diǎn)
- 當(dāng)響應(yīng)數(shù)據(jù)體量大時驾锰,避免瀏覽器出現(xiàn)忙等導(dǎo)致頁面出現(xiàn)長時間的空白
- 當(dāng)要發(fā)送的數(shù)據(jù)長度很難計算時,為了保證吞吐量走越,可以先將一部分?jǐn)?shù)據(jù)通過一個子包先發(fā)出去
4.4.3 缺點(diǎn)
- 協(xié)議比較復(fù)雜
4.4.4 抓包查看
4.5 KeepAlive
HTTP協(xié)議如果不做特殊處理都是一種短連接椭豫,即為一次會話建立TCP/IP之后,客戶端與服務(wù)端基于TCP/IP的連接之上通過HTTP協(xié)議完成會話后旨指,連接也會被服務(wù)端斷開赏酥。下一次的HTTP請求,還是需要為這個請求進(jìn)行三次握手創(chuàng)建TCP連接谆构,用完之后還需要四次揮手?jǐn)嚅_TCP連接裸扶,這大大減少了傳輸效率。
而KeepAlive的產(chǎn)生就是為了彌補(bǔ)上述的不足搬素。
HTTP1.0版本中需要在Header中加入Connection: keep-alive來指定會話完畢之后不立即斷開連接呵晨,而是有一定的復(fù)用時間魏保。HTTP1.1版本中,默認(rèn)開啟該配置:Connection: keep-alive摸屠。
4.5.1 Tomcat對KeepAlive的支持
-
KeepAliveTimeout
意義在于當(dāng)請求結(jié)束之后默認(rèn)要等待該值的時長谓罗,以達(dá)到復(fù)用的效果。
在Tomcat9中季二,該參數(shù)值默認(rèn)等于ConnectionTimeout的時長檩咱,而ConnectionTimeout在server.xml中默認(rèn)是20000ms。ConnectionTimeout的含義在于:當(dāng)連接建立起之后胯舷,可以忍受多久的時間客戶端沒有發(fā)數(shù)據(jù)過來刻蚯。
看一段Tomcat9的源碼:
<attribute name="keepAliveTimeout" required="false"> <p>The number of milliseconds this <strong>Connector</strong> will wait for another HTTP request before closing the connection. The default value is to use the value that has been set for the <strong>connectionTimeout</strong> attribute. Use a value of -1 to indicate no (i.e. infinite) timeout.</p> </attribute>
但是需要注意,該值最好別設(shè)置的太久桑嘶,當(dāng)大流量打入之后可能會導(dǎo)致資源始終無法釋放炊汹。
這里有一篇線上問題的復(fù)盤博客,可以參考一下:
-
MaxKeepAliveRequests
Tomcat某個時刻最大能維護(hù)多少的長鏈接不翩,這個值在Tomcat9版本中默認(rèn)為100兵扬。
看一段Tomcat9的源碼:
<attribute name="maxKeepAliveRequests" required="false"> <p>The maximum number of HTTP requests which can be pipelined until the connection is closed by the server. Setting this attribute to 1 will disable HTTP/1.0 keep-alive, as well as HTTP/1.1 keep-alive and pipelining. Setting this to -1 will allow an unlimited amount of pipelined or keep-alive HTTP requests. If not specified, this attribute is set to 100.</p> </attribute>
4.5.2 TCP協(xié)議與HTTP協(xié)議中的KeepAlive區(qū)別
需要注意的是:TCP協(xié)議中也有KeepAlive參數(shù),但是與HTTP的KeepAlive還是有區(qū)別的口蝠;
-
TCP-KeepAlive
TCP的KeepAlive是為了連接逼髦樱活,該機(jī)制有三個重要的參數(shù):
① tcp_keepalive_intvl:泵钫幔活探測消息的發(fā)送頻率
② tcp_keepalive_probes:需要發(fā)送X次的探測消息但依然無效傲霸,則認(rèn)為連接斷開
③ tcp_keepalive_time:最后一次數(shù)據(jù)交換到TCP第一次發(fā)送探針消息的間隔時間
-
HTTP-Keep-Alive
HTTP發(fā)出響應(yīng)之后不要斷開連接,連接需要保持一段時間眉反,以達(dá)到復(fù)用的效果昙啄。
4.5.3 如何復(fù)用長連接
首先需要了解的是,復(fù)用到底復(fù)用的是什么寸五,看了上面的介紹梳凛,短連接中的每次請求都需要先進(jìn)行TCP的連接導(dǎo)致TCP鏈路無法復(fù)用,因此這里的復(fù)用其實是針對TCP鏈路的復(fù)用梳杏。
可以配合連接池韧拒,如HttpClient就對長連接具有管理的功能,注意這里的管理肯定是針對長連接了十性,短連接壓根就無法復(fù)用叛溢。
4.5.4 如何關(guān)閉長鏈接
當(dāng)TCP連接需要關(guān)閉時,只需要在Header頭中添加Connection: close即可劲适。
4.5.5 疑問待解決
Tomcat9版本中默認(rèn)KeepTimeout為20s楷掉,我也查看了一下SpringBoot對于Tomcat的配置,發(fā)現(xiàn)SpringBoot沒有對Tomcat參數(shù)做調(diào)整霞势,至少KeepTimeout用的就是Tomcat默認(rèn)值烹植。
那么在這樣的情況下斑鸦,KeepTimeout最終應(yīng)該為20s才對,但是在抓包過程中卻發(fā)現(xiàn)其值為60s.
抓包如下:
4.5.6 從JVM層面判斷連接是否復(fù)用
TODO
五草雕、HTTP協(xié)議如何解決TCP的粘包與拆包
TCP協(xié)議鄙才,數(shù)據(jù)傳輸都是stream式的,數(shù)據(jù)之間是沒有流邊界的促绵,在這樣情況下自然就會出現(xiàn)粘包與拆包的問題。那么基于TCP之上的HTTP協(xié)議當(dāng)然也會面臨這樣的問題嘴纺,那它是如何解決的呢败晴?
了解了HTTP協(xié)議的結(jié)構(gòu)之后,解決粘包拆包的問題就迎刃而解了栽渴。
HTTP的請求與響應(yīng)尖坤,對于請求行、請求頭闲擦、響應(yīng)行慢味、響應(yīng)頭而言,都可以通過CRLF與空格作為流的邊界進(jìn)行讀取墅冷。
對于請求正文與響應(yīng)正文而言纯路,他們的字節(jié)長度在Header中的Content-Length中定義,假若寞忿,Header中沒有Content-Length屬性驰唬,取而代之的是Transfer-Encoding: chunked,那么就讀取每一個chunk的size腔彰,繼而再讀取size個byte叫编,知道讀取到last-chunk的size為0。