http1.1
http1.1的優(yōu)點(diǎn)
- 1. 簡(jiǎn)單
HTTP 基本的報(bào)文格式就是 header + body
,頭部信息也是 key-value
簡(jiǎn)單文本的形式,易于理解汪茧,降低了學(xué)習(xí)和使用的門檻椅亚。
- 2. 靈活和易于擴(kuò)展
HTTP 協(xié)議里的各類請(qǐng)求方法、URI/URL舱污、狀態(tài)碼玩郊、頭字段等每個(gè)組成要求都沒(méi)有被固定死办斑,都允許開(kāi)發(fā)人員自定義和擴(kuò)充挽鞠。
同時(shí) HTTP 由于是工作在應(yīng)用層( OSI
第七層)闹究,則它下層可以隨意變化,比如:
- HTTPS 就是在 HTTP 與 TCP 層之間增加了
SSL/TLS
安全傳輸層珠插; - HTTP/1.1 和 HTTP/2.0 傳輸協(xié)議使用的是 TCP 協(xié)議惧磺,而到了 HTTP/3.0 傳輸協(xié)議改用了
UDP 協(xié)議
。
- 3. 應(yīng)用廣泛和跨平臺(tái)
互聯(lián)網(wǎng)發(fā)展至今捻撑,HTTP 的應(yīng)用范圍非常的廣泛磨隘,從臺(tái)式機(jī)的瀏覽器到手機(jī)上的各種 APP,從看新聞顾患、刷貼吧到購(gòu)物番捂、理財(cái)、吃雞江解,HTTP 的應(yīng)用遍地開(kāi)花设预,同時(shí)天然具有跨平臺(tái)的優(yōu)越性。
http1.1的缺點(diǎn)
缺點(diǎn)很明顯
- 無(wú)狀態(tài)雙刃劍
無(wú)狀態(tài)的好處犁河,因?yàn)榉?wù)器不會(huì)去記憶 HTTP 的狀態(tài)鳖枕,所以不需要額外的資源來(lái)記錄狀態(tài)信息,這能減輕服務(wù)器的負(fù)擔(dān)呼股,能夠把更多的 CPU 和內(nèi)存用來(lái)對(duì)外提供服務(wù)耕魄。
無(wú)狀態(tài)的壞處画恰,既然服務(wù)器沒(méi)有記憶能力彭谁,它在完成有關(guān)聯(lián)性的操作時(shí)會(huì)非常麻煩。
對(duì)于無(wú)狀態(tài)的問(wèn)題允扇,解法方案有很多種缠局,其中比較簡(jiǎn)單的方式用 Cookie
技術(shù)和Session
技術(shù),而Session又是基于Cookie實(shí)現(xiàn)的考润,這里不過(guò)多介紹倆張方案的實(shí)現(xiàn)狭园。
- 明文傳輸
明文意味著在傳輸過(guò)程中的信息,是可方便閱讀的糊治,比如 Wireshark 抓包都可以直接肉眼查看唱矛,為我們調(diào)試工作帶了極大的便利性。
這正是這樣,HTTP 的所有信息都暴露在了光天化日下绎谦,相當(dāng)于信息裸奔
,也就是http傳輸非常不安全管闷。
- 傳輸過(guò)程數(shù)據(jù)可能被篡改
由于數(shù)據(jù)沒(méi)有進(jìn)行加密,傳輸過(guò)程都是明文窃肠,則很有可能被中間站攔截并篡改包个,所以http無(wú)法證明報(bào)文的完整性,且容易被篡改冤留。
- 無(wú)法驗(yàn)證通訊雙方身份
不驗(yàn)證通信雙方的身份碧囊,因此有可能遭遇偽裝并劫持流量。
對(duì)于無(wú)狀態(tài)
這一點(diǎn)特點(diǎn)來(lái)說(shuō)纤怒,不管是http1.1還是http2.0和http3.0都保持這一特點(diǎn)糯而,因?yàn)檫@一特點(diǎn)并不是完全是一個(gè)缺點(diǎn)。
而對(duì)于明文傳輸
泊窘、保證不了報(bào)文的完整性
和無(wú)法驗(yàn)證雙方身份
的這3個(gè)缺點(diǎn)歧蒋,https
基本都解決啦,https是在應(yīng)用層和傳輸層中間增加了一層SSL/TSL協(xié)議,https具體解決這3個(gè)問(wèn)題的實(shí)現(xiàn)方式這里也不過(guò)多介紹州既。但是需要注意的一點(diǎn)是普通的https僅驗(yàn)證了服務(wù)端的身份(通過(guò)CA證書(shū))谜洽,其實(shí)并沒(méi)有驗(yàn)證客戶端的身份,所以有的https服務(wù)仍然可以通過(guò)抓包工具獲取吴叶。
你可能也發(fā)現(xiàn)了阐虚,有的https服務(wù)可以被抓包,有的卻抓不到蚌卤,原因就是看https服務(wù)是否開(kāi)啟的雙向驗(yàn)證
实束,開(kāi)始了雙向驗(yàn)證之后不僅是在服務(wù)端需要按照證書(shū),客戶端也需要安裝證書(shū)逊彭,也就是只有開(kāi)啟了雙向驗(yàn)證的https來(lái)可以驗(yàn)證雙方身份咸灿,且無(wú)法被抓到工具獲取
。
http1.1的性能如何
HTTP 協(xié)議是基于 TCP/IP
侮叮,并且使用了「請(qǐng)求 - 應(yīng)答」
的通信模式避矢,所以性能的關(guān)鍵就在這兩點(diǎn)里。
「請(qǐng)求 - 應(yīng)答」
的通信模式是在同一個(gè)TCP連接
里客戶端發(fā)出一個(gè)請(qǐng)求之后只能等待該請(qǐng)求被響應(yīng)之后囊榜,客戶端才可以發(fā)生下一個(gè)請(qǐng)求审胸,如果上個(gè)請(qǐng)求一直沒(méi)有被響應(yīng),那么就是會(huì)被阻塞卸勺,可以發(fā)現(xiàn)http的性能的關(guān)鍵就在于此砂沛。
- 長(zhǎng)鏈接
早期 HTTP/1.0 性能上的一個(gè)很大的問(wèn)題,那就是每發(fā)起一個(gè)請(qǐng)求曙求,都要新建一次 TCP 連接(三次握手)碍庵,請(qǐng)求完成后映企,都要斷開(kāi)TCP鏈接(4次揮手),俗稱短鏈接静浴,而且是串行請(qǐng)求卑吭,做了無(wú)謂的 TCP 連接建立和斷開(kāi),增加了很大的通信開(kāi)銷马绝。
為了解決上述 TCP 連接問(wèn)題豆赏,HTTP/1.1 提出了長(zhǎng)連接
的通信方式,也叫持久連接
富稻,且是http1.1的默認(rèn)方式了掷邦。這種方式的好處在于減少了 TCP 連接的重復(fù)建立
和斷開(kāi)
所造成的額外開(kāi)銷,減輕了服務(wù)器端的負(fù)載椭赋。
短連接的特點(diǎn)是:只有任意一方執(zhí)行完代碼或者任意一方顯示的明確提出斷開(kāi)連接時(shí)則會(huì)斷開(kāi)鏈接抚岗。
長(zhǎng)連接的特點(diǎn)是:雙方代碼執(zhí)行完成后并不會(huì)斷開(kāi)鏈接,而只要任意一端顯示的明確提出斷開(kāi)連接哪怔,才會(huì)進(jìn)行4次揮手?jǐn)嚅_(kāi)TCP連接宣蔚。
- 管道傳輸
HTTP/1.1 默認(rèn)采用了長(zhǎng)連接
的方式,這使得管道
(pipeline)網(wǎng)絡(luò)傳輸成為了可能认境。
即可在同一個(gè) TCP 連接里面胚委,客戶端可以發(fā)起多個(gè)請(qǐng)求
,只要第一個(gè)請(qǐng)求發(fā)出去了叉信,不必等其回來(lái)亩冬,就可以發(fā)第二個(gè)請(qǐng)求出去,可以減少整體的響應(yīng)時(shí)間硼身。
還記得開(kāi)頭說(shuō)的「請(qǐng)求 - 應(yīng)答」
的通信模式嗎硅急?該模式下同一個(gè)tcp客戶端只能同時(shí)發(fā)送一個(gè)請(qǐng)求,只有該請(qǐng)求被響應(yīng)之后才可以發(fā)送下一個(gè)請(qǐng)求佳遂,這種模式對(duì)應(yīng)http的性能影響很大营袜,而管道傳輸
其實(shí)就是想解決這樣問(wèn)題的,但是遺憾的是管道傳輸
并沒(méi)有本質(zhì)上解決上述的問(wèn)題丑罪,原因繼續(xù)往下看荚板。
- 隊(duì)頭阻塞
舉例來(lái)說(shuō),客戶端需要請(qǐng)求兩個(gè)資源巍糯。以前的做法是啸驯,在同一個(gè) TCP 連接里面客扎,先發(fā)送 A 請(qǐng)求祟峦,然后等待服務(wù)器做出回應(yīng),收到后再發(fā)出 B 請(qǐng)求徙鱼。那么宅楞,管道機(jī)制
則是允許瀏覽器同時(shí)發(fā)出 A 請(qǐng)求和 B 請(qǐng)求
针姿,如下圖:
但是服務(wù)器必須按照接收請(qǐng)求的順序發(fā)送對(duì)這些管道化請(qǐng)求的響應(yīng)。
如果服務(wù)端在處理 A 請(qǐng)求時(shí)耗時(shí)比較長(zhǎng)厌衙,那么后續(xù)的請(qǐng)求的處理都會(huì)被阻塞住距淫,這稱為「隊(duì)頭堵塞」。所以管道傳輸
只解決了請(qǐng)求隊(duì)頭阻塞
而沒(méi)有解決響應(yīng)隊(duì)頭阻塞
婶希。也就是說(shuō)管道傳輸
其實(shí)很雞肋榕暇,沒(méi)什么卵用。
TIP:實(shí)際上 HTTP/1.1 管道化技術(shù)不是默認(rèn)開(kāi)啟
喻杈,而且瀏覽器基本都沒(méi)有支持彤枢。
有沒(méi)有想過(guò)為什么響應(yīng)時(shí)必須按照請(qǐng)求的順序返回呢?如果沒(méi)有按照請(qǐng)求的順序返回會(huì)發(fā)生什么情況呢筒饰?我舉個(gè)??缴啡,如果沒(méi)有按照請(qǐng)求順序響應(yīng)會(huì)發(fā)生什么結(jié)果:
如果有倆個(gè)請(qǐng)求a和b,請(qǐng)求a先到達(dá)服務(wù)器瓷们,但由于某種原因?qū)е耡的響應(yīng)被阻塞业栅,而請(qǐng)求b隨后到達(dá)服務(wù)器并得到了及時(shí)的響應(yīng),那么返回的響應(yīng)的順序其實(shí)發(fā)生了顛倒
谬晕。
客戶端可能會(huì)錯(cuò)誤地將屬于請(qǐng)求a和響應(yīng)b關(guān)聯(lián)碘裕,此時(shí)數(shù)據(jù)就會(huì)發(fā)生了錯(cuò)亂。這就導(dǎo)致客戶端難以確定哪個(gè)請(qǐng)求對(duì)應(yīng)哪個(gè)響應(yīng)攒钳,從而引發(fā)數(shù)據(jù)錯(cuò)亂娘汞。
這就是為什么要求響應(yīng)和請(qǐng)求的順序要一致,一致時(shí)就是可以順序匹配請(qǐng)求和響應(yīng)
夕玩,將不會(huì)出現(xiàn)上述的數(shù)據(jù)錯(cuò)亂問(wèn)題你弦。
本質(zhì)原因就是請(qǐng)求和響應(yīng)不能一一對(duì)應(yīng)而只能按照順序來(lái)匹配,可以繼續(xù)往下看燎孟,http2.0是怎么解決這個(gè)對(duì)應(yīng)的問(wèn)題的禽作。
http2.0
可以發(fā)現(xiàn)http1.1的性能其實(shí)很一般,http2.0對(duì)其做了很多改進(jìn)揩页,使性能發(fā)生了質(zhì)的提升旷偿。廢話不多說(shuō),上圖爆侣。
那 HTTP/2 相比 HTTP/1.1 性能上的改進(jìn):
- 內(nèi)置TLS協(xié)議萍程。
- 頭部壓縮:使用靜態(tài)表、動(dòng)態(tài)表和HPack實(shí)現(xiàn)兔仰。
- 二進(jìn)制格式:Header+Body都使用二進(jìn)制傳輸茫负。
- 并發(fā)傳輸:使用Stream、fream實(shí)現(xiàn)乎赴,Stream是實(shí)現(xiàn)并發(fā)傳輸?shù)年P(guān)鍵忍法。
- 服務(wù)器主動(dòng)推送資源潮尝。
頭部壓縮
HTTP 協(xié)議的報(bào)文是由「Header + Body」
構(gòu)成的,對(duì)于 Body 部分饿序,HTTP/1.1
協(xié)議可以使用頭字段 「Content-Encoding」
指定 Body 的壓縮方式勉失,比如用gzip
壓縮,這樣可以節(jié)約帶寬原探,但報(bào)文中的另外一部分 Header乱凿,是沒(méi)有針對(duì)它的優(yōu)化手段。
HTTP/1.1 報(bào)文中 Header 部分存在的問(wèn)題:
-
含很多固定的字段
咽弦,比如 Cookie告匠、User Agent、Accept 等离唬,這些字段加起來(lái)也高達(dá)幾百字節(jié)
甚至上千字節(jié)
后专,所以有必要壓縮
; -
大量的請(qǐng)求和響應(yīng)的報(bào)文里有很多字段和字段值都是
重復(fù)
的输莺,這樣會(huì)使得大量帶寬被這些冗余的數(shù)據(jù)占用了
戚哎,所以有必須要避免重復(fù)性
; - 字段是 ASCII 編碼的嫂用,雖然易于人類觀察型凳,但
效率低
,所以有必要改成二進(jìn)制編碼
嘱函;
HTTP/2 對(duì) Header 部分做了大改造甘畅,把以上的問(wèn)題都解決了。
HTTP/2 沒(méi)使用常見(jiàn)的 gzip
壓縮方式來(lái)壓縮頭部往弓,而是開(kāi)發(fā)了HPACK
算法疏唾,HPACK 算法主要包含三個(gè)組成部分:
靜態(tài)字典
動(dòng)態(tài)字典
-
Huffman 編碼
(壓縮算法)
客戶端和服務(wù)器兩端都會(huì)建立和維護(hù)「字典」
,用長(zhǎng)度較小的索引號(hào)
表示重復(fù)的字符串函似,再用Huffman 編碼壓縮數(shù)據(jù)
槐脏,可達(dá)到 50%~90% 的高壓縮率。
靜態(tài)字典
首先TCP連接建立后撇寞,客戶端和服務(wù)端都會(huì)有一張靜態(tài)字典
顿天,它是寫入到 HTTP/2 框架里的,不會(huì)變化的蔑担,靜態(tài)表里共有61 組
牌废,如下圖:
表中的
Index
表示索引(Key),Header Value
表示索引對(duì)應(yīng)的 Value啤握,Header Name
表示字段的名字鸟缕,比如 Index 為 2 代表Header頭中method: GET,Index 為 8 代表Header頭中的狀態(tài)碼 Status :200恨统。
你可能注意到叁扫,表中有的 Index 沒(méi)有對(duì)應(yīng)的 Header Value三妈,這是因?yàn)檫@些 Value 并不是固定
的而是變化的
畜埋,這些 Value 都會(huì)經(jīng)過(guò) Huffman 編碼
后莫绣,才會(huì)發(fā)送出去,具體是怎么實(shí)現(xiàn)的呢悠鞍?繼續(xù)往下看:
我們來(lái)看個(gè)具體的例子对室,比如Header頭中下面這個(gè) server 頭部字段,在 HTTP/1.1 的形式如下:
server: nghttpx\r\n
先給出結(jié)論:在http1.1中算上冒號(hào)空格和末尾的\r\n咖祭,共占用了
17 字節(jié)
掩宜,而使用了靜態(tài)表
和Huffman 編碼
,可以將它壓縮成8 字節(jié)
么翰,壓縮率大概47%
牺汤。
根據(jù) RFC7541 規(guī)范,如果頭部字段
屬于靜態(tài)表范圍
浩嫌,并且Value
是變化
的檐迟,整個(gè)頭部格式如下圖:
我抓了個(gè) HTTP/2 協(xié)議的網(wǎng)絡(luò)包,你可以從下圖看到码耐,高亮部分就是 server: nghttx 頭部字段的二進(jìn)制數(shù)據(jù)追迟,只用了 8 個(gè)字節(jié)而已。
對(duì)照著頭部格式來(lái)一步一步分析:
如果頭部字段屬于靜態(tài)表范圍骚腥,并且Value 是變化時(shí)敦间,
第一個(gè)字節(jié)
的前倆位固定為01
,后6位是頭部字段server
在靜態(tài)表中的索引值
,也就是54
束铭,轉(zhuǎn)化為二進(jìn)制為110110
,拼接起來(lái)之后第一個(gè)字節(jié)為01110110
廓块。第二個(gè)字節(jié)中的第一個(gè)位
H
表示Value 是否經(jīng)過(guò) Huffman 編碼,1
表示經(jīng)過(guò) Huffman 編碼契沫,0
則相反剿骨。后面的7個(gè)bit位表示Value 的長(zhǎng)度
,也就是nghttx
經(jīng)過(guò)huffman編碼后的長(zhǎng)度為7
,也就是0111
埠褪,至于為什么是7浓利,繼續(xù)看下面,這里寫暫且認(rèn)為是7,拼接上首位1后钞速,第二個(gè)字節(jié)表示為10000111
贷掖。-
接著計(jì)算
nghttx
的長(zhǎng)度。value
的值是通過(guò)huffman編碼
算出來(lái)的渴语,而Huffman 編碼的原理是將高頻出現(xiàn)的信息用「較短」的編碼表示苹威,從而縮減字符串長(zhǎng)度。
于是驾凶,在統(tǒng)計(jì)大量的 HTTP 頭部后牙甫,HTTP/2 根據(jù)
出現(xiàn)頻率
將 ASCII 碼編改為了Huffman 編碼表
掷酗,可以在RFC7541 文檔
找到這張靜態(tài) Huffman 表
,我就不把表的全部?jī)?nèi)容列出來(lái)了窟哺,我只列出字符串 nghttpx 中每個(gè)字符對(duì)應(yīng)的 Huffman 編碼泻轰,如下圖:
通過(guò)查表后,字符串nghttpx
的 Huffman 編碼在下圖看到且轨,共6
個(gè)字節(jié)浮声,每一個(gè)字符的 Huffman 編碼
,我用相同的顏色將他們對(duì)應(yīng)起來(lái)了旋奢,最后的 7 位是補(bǔ)位的
泳挥。
最終,server 頭部
的二進(jìn)制數(shù)據(jù)對(duì)應(yīng)的靜態(tài)頭部格式如下:
動(dòng)態(tài)字典
靜態(tài)表
只包含了61
種高頻
出現(xiàn)在頭部的字符串至朗,不在靜態(tài)表范圍內(nèi)的頭部字符串就要自行構(gòu)建動(dòng)態(tài)表
屉符,它的 Index
從62
起步,會(huì)在編碼解碼的時(shí)候隨時(shí)更新锹引,也就是靜態(tài)字典喝動(dòng)態(tài)字典是結(jié)合使用的矗钟。
比如,第一次發(fā)送請(qǐng)求時(shí)的request頭部中的「Cookie」
字段數(shù)據(jù)有上百個(gè)字節(jié)粤蝎,經(jīng)過(guò) Huffman 編碼
發(fā)送出去后真仲,客戶端就會(huì)更新自己的動(dòng)態(tài)字典
,添加一個(gè)Index 號(hào) 62
的數(shù)據(jù)初澎。
當(dāng)服務(wù)器收到請(qǐng)求之后會(huì)更新自己的動(dòng)態(tài)表
秸应,也添加一個(gè)新的 Index 號(hào) 62
。
那么在下一次請(qǐng)求的時(shí)候碑宴,就不用重復(fù)發(fā)這個(gè)字段的數(shù)據(jù)了软啼,只用發(fā) 1
個(gè)字節(jié)的 Index 號(hào)就好了,因?yàn)殡p方都可以根據(jù)自己的動(dòng)態(tài)表獲取到字段的數(shù)據(jù)延柠。
細(xì)心的人可能發(fā)現(xiàn)祸挪,如果客戶端請(qǐng)求發(fā)出后,由于網(wǎng)絡(luò)原因服務(wù)端并沒(méi)有收到請(qǐng)求贞间,此時(shí)會(huì)出現(xiàn)的情況是客戶端已經(jīng)更新Index為62的記錄贿条,而服務(wù)并沒(méi)有更新。
如果此時(shí)客戶端再次請(qǐng)求時(shí)增热,攜帶的是62的Index整以,而服務(wù)端收到62之后不清楚是什么意思,因?yàn)榉?wù)端并沒(méi)有存儲(chǔ)62的數(shù)據(jù)峻仇,此時(shí)就會(huì)出現(xiàn)問(wèn)題公黑,
這是http2.0的一個(gè)潛在問(wèn)題,http3.0對(duì)該問(wèn)題進(jìn)行了修復(fù)。
需要注意的是:新建的連接初始化時(shí)只有靜態(tài)表凡蚜,只有在同一個(gè)連接上后續(xù)的請(qǐng)求時(shí)才會(huì)動(dòng)態(tài)的增加動(dòng)態(tài)字典人断,連接銷毀時(shí)對(duì)應(yīng)的動(dòng)態(tài)字典也就隨之消失。
如果消息字段在 1 個(gè)連接上只發(fā)送了 1 次朝蜘,或者重復(fù)傳輸時(shí)恶迈,字段總是略有變化,動(dòng)態(tài)表就無(wú)法被充分利用了芹务。
因此蝉绷,隨著在同一 HTTP/2 連接上發(fā)送的報(bào)文越來(lái)越多鸭廷,客戶端和服務(wù)器雙方的「字典」積累的越來(lái)越多枣抱,理論上最終每個(gè)頭部字段都會(huì)變成 1 個(gè)字節(jié)的 Index,這樣便避免了大量的冗余數(shù)據(jù)的傳輸辆床,大大節(jié)約了帶寬佳晶。
理想很美好,現(xiàn)實(shí)很骨感讼载。動(dòng)態(tài)表越大轿秧,占用的內(nèi)存也就越大
,如果占用了太多內(nèi)存咨堤,是會(huì)影響服務(wù)器性能的菇篡,因此 Web 服務(wù)器都會(huì)提供類似 http2_max_requests
的配置,用于限制一個(gè)連接上能夠傳輸?shù)恼?qǐng)求數(shù)量一喘,避免動(dòng)態(tài)表無(wú)限增大
驱还,請(qǐng)求數(shù)量到達(dá)上限后,就會(huì)關(guān)閉 HTTP/2 連接來(lái)釋放內(nèi)存凸克。
綜上议蟆,HTTP/2 頭部的編碼通過(guò)「靜態(tài)表、動(dòng)態(tài)表萎战、Huffman 編碼」共同完成的咐容。
二進(jìn)制
HTTP/2 厲害的地方在于將 HTTP/1 的文本格式改成二進(jìn)制格式傳輸數(shù)據(jù),極大提高了 HTTP 傳輸效率蚂维,而且二進(jìn)制數(shù)據(jù)使用位運(yùn)算能高效解析戳粒。
二進(jìn)制數(shù)據(jù)傳輸?shù)幕締挝皇?code>二進(jìn)制幀,即為fream
,下圖為fream
的結(jié)構(gòu)
- 幀開(kāi)頭的前 3 個(gè)字節(jié)表示幀數(shù)據(jù)(Frame Playload)的長(zhǎng)度虫啥。
- 幀長(zhǎng)度后面的一個(gè)字節(jié)是表示
幀的類型
蔚约,HTTP/2 總共定義了 10 種類型的幀,一般分為數(shù)據(jù)幀
和控制幀
兩類孝鹊,如下表格:
我們主要關(guān)注數(shù)據(jù)幀
,我們知道http2.0中的·Header+Body·都是使用·二進(jìn)制幀·來(lái)實(shí)現(xiàn)炊琉,如果幀的類型為HEADRERS
則表示該幀的數(shù)據(jù)為·Header數(shù)據(jù)·,如果幀的類型為DATA
則表示該幀的數(shù)據(jù)為Body
數(shù)據(jù)。
幀的類型
主要的作用是表明該數(shù)據(jù)是什么類型的數(shù)據(jù)苔咪。 - 幀類型后面的一個(gè)字節(jié)是
標(biāo)志位
锰悼,可以保存 8 個(gè)標(biāo)志位,用于攜帶簡(jiǎn)單的控制信息团赏,比如:-
END_HEADERS
表示頭數(shù)據(jù)結(jié)束標(biāo)志箕般,相當(dāng)于 HTTP/1 里頭后的空行(“\r\n”); -
END_Stream
表示單方向數(shù)據(jù)發(fā)送結(jié)束舔清,后續(xù)不會(huì)再有數(shù)據(jù)幀丝里。 -
PRIORITY
表示流的優(yōu)先級(jí);
-
該
標(biāo)志位
的作用是非常重要的体谒,想象一個(gè)http請(qǐng)求被分成了多個(gè)幀數(shù)據(jù)
發(fā)生,服務(wù)端在接受到這么多幀數(shù)據(jù)
的時(shí)杯聚,可以根據(jù)數(shù)據(jù)幀的類別
區(qū)別出哪些幀是Header數(shù)據(jù)哪些是Body數(shù)據(jù)。但是
一個(gè)完整的Header數(shù)據(jù)或者Body數(shù)據(jù)是被分成多個(gè)幀數(shù)據(jù)發(fā)送
的抒痒,服務(wù)端是當(dāng)收到幀類別為HEADRERS
且標(biāo)志位為END_HEADERS
時(shí)表示該請(qǐng)求的header數(shù)據(jù)已經(jīng)全部接受完成了幌绍,可以處理header請(qǐng)求了;同理當(dāng)收到幀類別為
DATA
且標(biāo)識(shí)位為END_Stream
時(shí)表示該請(qǐng)求的body數(shù)據(jù)全部發(fā)生完成了故响,可以處理body請(qǐng)求了傀广。可以發(fā)現(xiàn)標(biāo)志位的作用非常重要,但是有沒(méi)有發(fā)現(xiàn)一點(diǎn)彩届,如果幀數(shù)據(jù)發(fā)生順序錯(cuò)亂伪冰,會(huì)發(fā)生嚴(yán)重的問(wèn)題,比如當(dāng)收到了一個(gè)幀類別為
HEADRERS
且標(biāo)志位為END_HEADERS
的幀數(shù)據(jù)時(shí)樟蠕,就代表著header頭數(shù)據(jù)發(fā)生完畢了贮聂,但是由于網(wǎng)絡(luò)原因?qū)е缕渲械囊粋€(gè)header幀數(shù)據(jù)又發(fā)生過(guò)來(lái)了,這不出現(xiàn)很重的問(wèn)題了嗎坯墨?我已經(jīng)認(rèn)為header數(shù)據(jù)發(fā)生完畢了寂汇,然后過(guò)一會(huì),你發(fā)過(guò)來(lái)一個(gè)header數(shù)據(jù)捣染,我該怎么處理啊骄瓣,全亂了。
所以同一個(gè)stream里的幀數(shù)據(jù)是嚴(yán)格要求順序的發(fā)送的耍攘,不可以亂序發(fā)送的榕栏,時(shí)序問(wèn)題由tcp的順序性保證(序列號(hào))。
- 幀頭的最后 4 個(gè)字節(jié)是
流標(biāo)識(shí)符
(Stream ID),最高位被保留不用蕾各,只有 31 位可以使用扒磁,因此流標(biāo)識(shí)符的最大值是 2^31,大約是 21 億式曲,它的作用是用來(lái)標(biāo)識(shí)該Frame
屬于哪個(gè)Stream
妨托,接收方可以根據(jù)這個(gè)信息從亂序的幀(這里的亂序是指所屬不同Stream的Fream是亂序的缸榛,但是同一個(gè)Stream內(nèi)的Fream肯定是有序的)里找到相同 Stream ID 的幀,從而有序組裝信息兰伤。
如果你不懂Stream和Fream的關(guān)系内颗,那么繼續(xù)往下看
并發(fā)傳輸
知道了 HTTP/2 的幀結(jié)構(gòu)
后,我們?cè)賮?lái)看看它是如何實(shí)現(xiàn)并發(fā)傳輸?shù)摹?/p>
我們都知道 HTTP/1.1 的實(shí)現(xiàn)是基于請(qǐng)求-響應(yīng)模型的敦腔。同一個(gè)TCP連接中
均澳,HTTP 完成一個(gè)事務(wù)(請(qǐng)求與響應(yīng)),才能處理下一個(gè)事務(wù)符衔,也就是說(shuō)在發(fā)出請(qǐng)求等待響應(yīng)的過(guò)程中找前,是沒(méi)辦法做其他事情的,如果響應(yīng)遲遲不來(lái)判族,那么后續(xù)的請(qǐng)求是無(wú)法發(fā)送的躺盛,也造成了隊(duì)頭阻塞
的問(wèn)題,這也是http1.1的性能關(guān)鍵五嫂。
而 HTTP/2 就很牛逼了颗品,引出了 Stream
概念肯尺,多個(gè) Stream 可復(fù)用在一條 TCP 連接沃缘。實(shí)現(xiàn)了在同一個(gè)TCP連接上可以并發(fā)多個(gè)請(qǐng)求和響應(yīng)。
為了理解 HTTP/2 的并發(fā)是怎樣實(shí)現(xiàn)的则吟,我們先來(lái)理解 HTTP/2 中的 Stream槐臀、Message、Frame 這 3 個(gè)概念氓仲。
你可以從上圖中看到:
- 1 個(gè) TCP 連接包含一個(gè)或者多個(gè) Stream水慨,Stream 是 HTTP/2 并發(fā)的關(guān)鍵技術(shù)。一個(gè)TCP連接由相同的四元組組成敬扛,即源ip晰洒、源端口、目標(biāo)ip和目標(biāo)端口啥箭,也就是客戶端并發(fā)請(qǐng)求谍珊,服務(wù)端也可以并發(fā)響應(yīng);
- Stream 里可以包含 1 個(gè)或多個(gè) Message急侥,Message 對(duì)應(yīng) HTTP/1 中的請(qǐng)求或響應(yīng)砌滞,由 HTTP 頭部和包體構(gòu)成;
- Message 里包含一條或者多個(gè) Frame坏怪,F(xiàn)rame 是 HTTP/2 最小單位贝润,以二進(jìn)制壓縮格式存放 HTTP/1 中的內(nèi)容(頭部和包體);
我估計(jì)你看到這里還是云里霧里的铝宵,為什么Stream能實(shí)現(xiàn)并發(fā)傳輸呢打掘?Stream到底是個(gè)什么呢?
想知道這個(gè)問(wèn)題的答案,先來(lái)回顧下http1.1中開(kāi)啟管道傳輸之后為什么只支持客戶端并發(fā)請(qǐng)求而不支持服務(wù)端的并發(fā)響應(yīng)呢尊蚁?根本原因就是服務(wù)端的響應(yīng)和客戶端的請(qǐng)求對(duì)應(yīng)不上唯绍,只能根據(jù)請(qǐng)求和響應(yīng)的順序來(lái)匹配。
而Stream
其實(shí)是一個(gè)唯一的ID標(biāo)識(shí)
枝誊,在同一個(gè)tcp連接中的 Stream ID 是不能復(fù)用的况芒,只能順序遞增
,Stream
可以理解為并不是一個(gè)真實(shí)存在的東西叶撒,就是一個(gè)唯一的自增ID绝骚。
我們知道幀數(shù)據(jù)Fream
是數(shù)據(jù)傳輸?shù)幕締挝唬?code>Fream結(jié)構(gòu)中有個(gè)流標(biāo)識(shí)符
是用來(lái)表示該Fream
所屬的Stream ID
祠够。
客戶端的每次請(qǐng)求都會(huì)分配一個(gè)唯一的Stream ID
压汪。比如客戶端發(fā)出一個(gè)請(qǐng)求,Stream ID為123
古瓤,而請(qǐng)求體中Header+Body
會(huì)被分割成多個(gè)Fream
止剖,且這些Fream
的標(biāo)識(shí)符都將是123
;當(dāng)服務(wù)端響應(yīng)數(shù)據(jù)時(shí)落君,也會(huì)將響應(yīng)體中的Header+Body
分割成多個(gè)Fream
穿香,且這些響應(yīng)體的所有Fream
的標(biāo)識(shí)符也同樣是123
。
看到這里绎速,我想你應(yīng)該明白了Stream是怎么實(shí)現(xiàn)并發(fā)傳輸?shù)牧税善せ瘛ttp1.1中由于不知道返回的響應(yīng)是屬于哪個(gè)請(qǐng)求的,所以只能默認(rèn)按照順序匹配纹冤。而http2.0中請(qǐng)求和響應(yīng)共用一個(gè)Stream ID,這樣就可以將請(qǐng)求和響應(yīng)進(jìn)行精準(zhǔn)匹配啦洒宝。
因此,我們可以得出個(gè)結(jié)論:多個(gè) Stream 跑在一條 TCP 連接萌京,同一個(gè) HTTP 請(qǐng)求與響應(yīng)是跑在同一個(gè) Stream 中雁歌,HTTP 消息可以由多個(gè) Frame 構(gòu)成, 一個(gè) Frame 可以由多個(gè) TCP 報(bào)文構(gòu)成知残。
在 HTTP/2 連接上靠瞎,不同 Stream 的幀是可以亂序發(fā)送的(因此可以并發(fā)不同的 Stream )
,因?yàn)槊總€(gè)幀的頭部會(huì)攜帶 Stream ID 信息橡庞,所以接收端可以通過(guò) Stream ID 有序組裝成 HTTP 消息较坛,而同一 Stream 內(nèi)部的幀必須是嚴(yán)格有序的
,在上面幀數(shù)據(jù)結(jié)構(gòu)介紹標(biāo)志位
的說(shuō)明為什么必須保持有序。
比如下圖扒最,服務(wù)端并行亂序
的地發(fā)送了兩個(gè)響應(yīng): Stream 1 和 Stream 3丑勤,這兩個(gè) Stream 都是跑在一個(gè) TCP 連接上,客戶端收到后吧趣,會(huì)根據(jù)相同的 Stream ID 有序組裝成 HTTP 消息,并將組裝完成的消息發(fā)送給相同Stream ID 的請(qǐng)求法竞。
客戶端和服務(wù)器雙方都可以建立 Stream耙厚,因?yàn)?code>服務(wù)端可以主動(dòng)推送資源給客戶端。
客戶端建立的 Stream 必須是奇數(shù)號(hào)
岔霸,而服務(wù)器建立的 Stream 必須是偶數(shù)號(hào)
薛躬。
同一個(gè)連接
中的 Stream ID 是不能復(fù)用
的,只能順序遞增呆细。從幀結(jié)構(gòu)
中可以發(fā)現(xiàn)型宝,流標(biāo)識(shí)符
(Stream ID),只有 31 位可以使用絮爷,因此流標(biāo)識(shí)符的最大值是 2^31趴酣,大約是 21 億
,所以當(dāng) Stream ID 耗盡時(shí),需要發(fā)一個(gè)控制幀 GOAWAY
坑夯,用來(lái)優(yōu)雅關(guān)閉 TCP 連接岖寞。
服務(wù)器主動(dòng)推送資源
HTTP/1.1 不支持服務(wù)器主動(dòng)推送資源給客戶端,都是由客戶端向服務(wù)器發(fā)起請(qǐng)求后柜蜈,才能獲取到服務(wù)器響應(yīng)的資源仗谆。
比如,客戶端通過(guò) HTTP/1.1 請(qǐng)求從服務(wù)器那獲取到了 HTML 文件淑履,而 HTML 可能還需要依賴 CSS 來(lái)渲染頁(yè)面隶垮,這時(shí)客戶端還要再發(fā)起獲取 CSS 文件的請(qǐng)求,需要兩次消息往返鳖谈,如下圖左邊部分:
如上圖右邊部分岁疼,在 HTTP/2 中,客戶端在訪問(wèn) HTML 時(shí)缆娃,服務(wù)器可以直接主動(dòng)推送 CSS 文件,減少了消息傳遞的次數(shù)瑰排,
減少了網(wǎng)絡(luò)耗時(shí)
贯要。
在 Nginx 中,如果你希望客戶端訪問(wèn) /test.html 時(shí)椭住,服務(wù)器直接推送 /test.css崇渗,那么可以這么配置:
location /test.html {
http2_push /test.css;
}
http2.0存在的缺點(diǎn)
http2.0相對(duì)于http1.1性能確實(shí)提升了很多,但是仍然還存在一些問(wèn)題
- 對(duì)頭阻塞
- TCP和TLS的握手延時(shí)
- 網(wǎng)絡(luò)遷移需要重新建立連接
TCP 對(duì)頭阻塞
HTTP/2 多個(gè)請(qǐng)求是跑在一個(gè) TCP 連接中的京郑,那么當(dāng) TCP 丟包時(shí)宅广,整個(gè) TCP 都要等待重傳,那么就會(huì)阻塞該 TCP 連接中的所有請(qǐng)求些举。
比如下圖中跟狱,Stream 2 有一個(gè) TCP 報(bào)文丟失了,那么即使收到了 Stream 3 和 Stream 4 的 TCP 報(bào)文,應(yīng)用層也是無(wú)法讀取讀取的,相當(dāng)于阻塞了 Stream 3 和 Stream 4 請(qǐng)求座掘。
因?yàn)镠TTP2.0是基于TCP 是字節(jié)流協(xié)議昔案,TCP 層必須保證收到的字節(jié)數(shù)據(jù)是完整且有序的欣范,如果序列號(hào)較低的 TCP 段在網(wǎng)絡(luò)傳輸中丟失了戒悠,即使序列號(hào)較高的 TCP 段已經(jīng)被接收了烦衣,也會(huì)被阻塞在
傳輸層
嚷闭,應(yīng)用層
也無(wú)法從內(nèi)核中讀取到這部分?jǐn)?shù)據(jù)纵寝,只有當(dāng)所有的TCP段都被接受成功之后论寨,才會(huì)將數(shù)據(jù)組裝之后發(fā)送給應(yīng)用層
,應(yīng)用層
才會(huì)讀取數(shù)據(jù)爽茴。
可以發(fā)現(xiàn)出現(xiàn)阻塞的本質(zhì)原因是:阻塞出現(xiàn)在了傳輸層政基,而導(dǎo)致應(yīng)用層讀不到數(shù)據(jù)。
那么如果讓阻塞出現(xiàn)在應(yīng)用層而不要出現(xiàn)在網(wǎng)絡(luò)層是不是就可以避免對(duì)頭阻塞了闹啦,可以看下http3.0的實(shí)現(xiàn)沮明。
可以發(fā)現(xiàn)Http2.0出現(xiàn)對(duì)頭阻塞的場(chǎng)景只發(fā)送在Tcp丟包
的場(chǎng)景下。
可以發(fā)現(xiàn)http2.0不管怎么優(yōu)化也解決不了TCP的對(duì)頭阻塞
問(wèn)題窍奋,除非把TCP協(xié)議換了荐健, 這是TCP 協(xié)議固有的問(wèn)題×瞻溃可以接續(xù)往下看http3.0是怎么解決TCP對(duì)頭阻塞的江场。
TCP和TLS的握手延時(shí)
發(fā)起 HTTP 請(qǐng)求時(shí),需要經(jīng)過(guò) TCP 三次握手
和 TLS 四次握手
(TLS 1.2)的過(guò)程窖逗,因此共需要 3 個(gè) RTT 的時(shí)延才能發(fā)出請(qǐng)求數(shù)據(jù)址否。
另外,TCP 由于具有「擁塞控制」
的特性碎紊,所以剛建立連接的 TCP 會(huì)有個(gè)「慢啟動(dòng)」
的過(guò)程佑附,它會(huì)對(duì) TCP 連接產(chǎn)生“減速”效果,給人的感覺(jué)就是突然卡頓了一下仗考。
網(wǎng)絡(luò)遷移需要重新建立連接
一個(gè) TCP 連接
是由四元組
(源 IP 地址音同,源端口,目標(biāo) IP 地址秃嗜,目標(biāo)端口)確定的权均,這意味著如果 IP 地址或者端口變動(dòng)了,就會(huì)導(dǎo)致需要 TCP 與 TLS 重新握手锅锨,這不利于移動(dòng)設(shè)備切換網(wǎng)絡(luò)的場(chǎng)景叽赊,比如 4G 網(wǎng)絡(luò)環(huán)境切換成 WiFi。
這些問(wèn)題都是 TCP 協(xié)議固有的問(wèn)題必搞,無(wú)論應(yīng)用層的 HTTP/2 在怎么設(shè)計(jì)都無(wú)法逃脫必指。要解決這個(gè)問(wèn)題,就必須把傳輸層協(xié)議替換成 UDP顾画,這個(gè)大膽的決定取劫,HTTP/3 做了匆笤!
http3.0
http2.0有3個(gè)缺陷,http3.0都給解決了谱邪,接下來(lái)看下是怎么一步一步解決的炮捧。
可以發(fā)現(xiàn)http3.0有幾個(gè)特點(diǎn)
- 傳輸層由TCP改用成UDP協(xié)議。我們深知惦银,UDP 是一個(gè)簡(jiǎn)單咆课、不可靠的傳輸協(xié)議,而且是 UDP 包之間是無(wú)序的扯俱,也沒(méi)有依賴關(guān)系书蚪。而且,UDP 是不需要連接的迅栅,也就不需要握手和揮手的過(guò)程殊校,所以天然的就比 TCP 快。
-
應(yīng)用層
新增QUIC
協(xié)議: 雖然UDP是不具備可靠性等特性的读存,但是UDP的上層協(xié)議QUID
为流,它具有類似 TCP 的連接管理、擁塞窗口让簿、流量控制的網(wǎng)絡(luò)特性敬察,相當(dāng)于將不可靠傳輸?shù)?UDP 協(xié)議變成“可靠”的了,所以不用擔(dān)心數(shù)據(jù)包丟失的問(wèn)題尔当。 - TLS協(xié)議內(nèi)置在了
QUIC
協(xié)議中莲祸。避免了TLS的4次握手。 - 由HTTP2.0中的
HPACK
編碼格式改為了QPACK
編碼格式椭迎。
無(wú)隊(duì)頭阻塞
QUIC 協(xié)議
也有類似 HTTP/2 Stream 與多路復(fù)用
的概念锐帜,也是可以在同一條連接上并發(fā)傳輸多個(gè) Stream的,這個(gè)優(yōu)點(diǎn)http3.0是繼承了的侠碧。
在來(lái)回憶下抹估,為什么HTTP2.0會(huì)有TCP對(duì)頭阻塞?
主要原因是:當(dāng)tcp發(fā)送丟包時(shí),tcp所在的傳輸層會(huì)發(fā)生阻塞弄兜,進(jìn)而應(yīng)用層也發(fā)生阻塞。只有組裝完成所有tcp段之后瓷式,應(yīng)用層才會(huì)讀取替饿。
而上面的http3.0介紹中TCP改成了UDP,而UDP并不是對(duì)數(shù)據(jù)的連續(xù)性,時(shí)序性等問(wèn)題進(jìn)行驗(yàn)證贸典,所以傳輸層肯定是不會(huì)發(fā)生阻塞了视卢。
因?yàn)閁DP是不可靠的,所以在應(yīng)用層
實(shí)現(xiàn)了QUIC
協(xié)議來(lái)保證消息的可靠性廊驼。http2.0和http3.0對(duì)比可發(fā)現(xiàn)一點(diǎn):阻塞發(fā)生的層級(jí)由http2.0的傳輸層變更到了http3.0的應(yīng)用層
据过。正是由于這一變更惋砂,如果http3.0請(qǐng)求時(shí)發(fā)生丟包則應(yīng)用層只會(huì)阻塞本請(qǐng)求,后續(xù)的請(qǐng)求是不會(huì)阻塞的绳锅,應(yīng)用層都是可以讀取到的西饵;同理,如果ttp3.0響應(yīng)時(shí)發(fā)生丟包則應(yīng)用層也只會(huì)阻塞本響應(yīng)鳞芙,后續(xù)的響應(yīng)是不會(huì)阻塞的眷柔,應(yīng)用層都是可以讀取到的。
所以原朝,QUIC 連接上的多個(gè) Stream 之間并沒(méi)有依賴驯嘱,都是獨(dú)立的,某個(gè)流發(fā)生丟包了喳坠,只會(huì)影響該流鞠评,其他流不受影響。
更快的連接建立
對(duì)于 HTTP/1 和 HTTP/2 協(xié)議壕鹉,TCP 和 TLS 是分層的剃幌,分別屬于內(nèi)核實(shí)現(xiàn)的傳輸層、OpenSSL 庫(kù)實(shí)現(xiàn)的表示層御板,因此它們難以合并在一起锥忿,需要分批次來(lái)握手,先 3次
TCP 握手怠肋,再 4次
TLS 握手敬鬓。
HTTP/3 在傳輸數(shù)據(jù)前雖然需要 QUIC 協(xié)議握手,這個(gè)握手過(guò)程只需要 1 RTT
笙各,握手的目的是為確認(rèn)雙方的「連接 ID」钉答,連接遷移就是基于連接 ID 實(shí)現(xiàn)的。
但是 HTTP/3 的 QUIC 協(xié)議并不是與 TLS 分層
杈抢,而是 QUIC 內(nèi)部包含了 TLS
数尿,它在自己的幀會(huì)攜帶 TLS 里的“記錄”,再加上 QUIC 使用的是 TLS 1.3惶楼,因此僅需 1 個(gè) RTT
就可以「同時(shí)」完成建立連接與密鑰協(xié)商右蹦,甚至在第二次連接的時(shí)候,應(yīng)用數(shù)據(jù)包可以和 QUIC 握手信息(連接信息 + TLS 信息)一起發(fā)送歼捐,達(dá)到0-RTT
的效果何陆。
至于為什么第二次連接時(shí)為 0-RTT
,接著繼續(xù)看下面豹储。
網(wǎng)絡(luò)遷移
在前面我們提到贷盲,基于 TCP 傳輸協(xié)議的 HTTP 協(xié)議,由于是通過(guò)四元組(源 IP剥扣、源端口巩剖、目的 IP铝穷、目的端口)確定一條 TCP 連接。
那么當(dāng)移動(dòng)設(shè)備的網(wǎng)絡(luò)從 4G 切換到 WiFi 時(shí)佳魔,意味著 IP 地址變化了曙聂,那么就必須要斷開(kāi)連接,然后重新建立連接吃引,而建立連接的過(guò)程包含 TCP 三次握手和 TLS 四次握手的時(shí)延筹陵,以及 TCP 慢啟動(dòng)的減速過(guò)程,給用戶的感覺(jué)就是網(wǎng)絡(luò)突然卡頓了一下镊尺,因此連接的遷移成本是很高的朦佩。
而 QUIC 協(xié)議沒(méi)有用四元組
的方式來(lái)“綁定”連接
,而是通過(guò)連接 ID
來(lái)標(biāo)記通信的兩個(gè)端點(diǎn)
庐氮,客戶端和服務(wù)器可以各自選擇一組 ID 來(lái)標(biāo)記自己语稠,因此即使移動(dòng)設(shè)備的網(wǎng)絡(luò)變化后,導(dǎo)致 IP 地址變化了弄砍,只要仍保有上下文信息(比如連接 ID仙畦、TLS 密鑰等),就可以“無(wú)縫”地復(fù)用原連接音婶,消除重連的成本慨畸,沒(méi)有絲毫卡頓感,達(dá)到了連接遷移的功能衣式。