時(shí)代之風(fēng)(下):HTTP/2內(nèi)核剖析

透視HTTP協(xié)議


image.png
image.png

由于http/2 “事實(shí)上” 是基于TLS,所有在正式收發(fā)數(shù)據(jù)之前仲吏,會(huì)有TCP握和TLS握手诅挑,這兩個(gè)步驟相信你一定已經(jīng)很熟悉了

TLS握手成功之前返帕,客戶端必須要發(fā)送一個(gè) “連接前言” 夫嗓,用來(lái)確認(rèn)建立HTTP/2 連接

這個(gè)“連接前言”是標(biāo)準(zhǔn)的 HTTP/1 請(qǐng)求報(bào)文迟螺,使用純文本的ASCII碼格式;請(qǐng)求方法是特別注冊(cè)一個(gè)關(guān)鍵字 “PRI”舍咖;全文只有24個(gè)字節(jié)

PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n

在 wireshark里矩父,http/2 的連接前言被稱為 Magic 意思是 不可知的魔法
所以,就不要問(wèn)“為什么會(huì)是這樣”了排霉,只要服務(wù)器收到這個(gè)“有魔力的字符串”窍株,就知道客戶端在TLS上想要的是是HTTP/2協(xié)議,而不是其他別的協(xié)議,后面就會(huì)都使用 HTTP/2 的數(shù)據(jù)格式夹姥。

頭部壓縮:
確認(rèn)了連接了,HTTP/2就開(kāi)始準(zhǔn)備請(qǐng)求報(bào)文

因?yàn)檎Z(yǔ)義上它與HTTP/1兼容辙诞,所以報(bào)文還是由 “Header+Body”構(gòu)成的辙售,但在請(qǐng)求發(fā)送前,必須用“HPACK”算法來(lái)壓縮頭部數(shù)據(jù)飞涂。
“HPACK”算法是專門壓縮HTTP頭部制定的算法旦部。與gzip、zlib 等壓縮算法不同较店,它是一個(gè)“有狀態(tài)”的算法士八,需要客戶端和服務(wù)器各自維護(hù)一份“索引表”,也可以說(shuō)是“字典”(這有點(diǎn)類似brotli),壓縮和解壓縮就是查表和更新表的操作梁呈。

為了方便管理和壓縮婚度,HTTP/2 廢除了原有的起始行的概念,把起始行里面的請(qǐng)求方法官卡,URI蝗茁、狀態(tài)碼等統(tǒng)一轉(zhuǎn)換成了頭字段形式,并且給這些“不是頭字段的頭字段”起了個(gè)特別的名字——“偽頭字段”寻咒。而起始行里的版本號(hào)和錯(cuò)誤原因短語(yǔ)因?yàn)闆](méi)什么大用哮翘,順便也給廢除了

現(xiàn)在http 報(bào)文就簡(jiǎn)單了,全都是“key-value”形式的字段毛秘,于是HTTP/2 就為一些最常用的頭字段定義了一個(gè)只讀的“靜態(tài)表”

下面的這個(gè)表格列出了“靜態(tài)表”的一部分饭寺,這樣只要查表就可以知道字段名和對(duì)應(yīng)的值,
比如 數(shù)字 “2” 代表 “GET”, 數(shù)字“8” 代表狀態(tài)碼“200”


image.png

但是如果表里只有key 沒(méi)有value,或者是自定義字段根本找不到該怎么辦呢叫挟?

這就要用到 “動(dòng)態(tài)表”艰匙,他添加在靜態(tài)表后面,結(jié)構(gòu)相同霞揉,但會(huì)在編碼解碼的時(shí)候隨時(shí)更新旬薯。

比如說(shuō),第一次發(fā)送請(qǐng)求時(shí)的“user-agent”字段長(zhǎng)是一百多個(gè)字節(jié)适秩,用哈夫曼壓縮編碼發(fā)送之后绊序,客戶端和服務(wù)端都更新自己的動(dòng)態(tài)表,添加一個(gè)新的索引號(hào)“65”秽荞。 那么下一次發(fā)送的時(shí)候就不用再重復(fù)發(fā)那么多字節(jié)了骤公,只要用一個(gè)字節(jié)發(fā)送變化就好。

image.png

你可以想象得出來(lái)扬跋,隨著在HTTP/2 連接上發(fā)送的報(bào)文越來(lái)越多,兩邊的“字典”也會(huì)越來(lái)越豐富阶捆,最終每次的頭部字段都會(huì)變成一兩個(gè)字節(jié)的代碼,原來(lái)上千字節(jié)的頭用幾十字節(jié)就可以表示了,壓縮效果比gzip要好多了洒试。

二進(jìn)制幀
頭部數(shù)據(jù)壓縮之后倍奢,HTTP/2 就要把報(bào)文拆成二進(jìn)制的幀準(zhǔn)備發(fā)送

HTTP/2 的幀結(jié)構(gòu)有點(diǎn)類似TCP的端或者TLS里的記錄,但報(bào)頭很小垒棋,只有9字節(jié)卒煞,非常地節(jié)省(可以對(duì)比一下TCP頭叼架,它最少是20個(gè)字節(jié))

二進(jìn)制的格式也保證了不會(huì)有歧義畔裕,而且使用位運(yùn)算能夠非常簡(jiǎn)單高效地解析。

image.png

幀開(kāi)頭是3個(gè)字節(jié)的長(zhǎng)度(但不包括頭的9個(gè)字節(jié))乖订,默認(rèn)上限是214扮饶,最大是224,也就是說(shuō)HTTP/2 的幀通常不超過(guò)16K,最大是16M

長(zhǎng)度后面的一個(gè)字節(jié)是 幀類型 乍构,大致可以分為 數(shù)據(jù)幀和控制幀兩類甜无,HEADERS幀和DATA幀屬于數(shù)據(jù)幀,存放的是HTTP報(bào)文蜡吧,而SETTINGS,PING毫蚓、PRIORITY 等則是用來(lái)管理流的控制幀。

HTTP/2 總共定義了10中類型的幀昔善,但一個(gè)字節(jié)可以表示最多256種元潘,所以也允許在標(biāo)準(zhǔn)之外定義其他類型實(shí)現(xiàn)功能擴(kuò)展。這就有點(diǎn)像TLS里擴(kuò)展協(xié)議的意思了君仆,比如Google 的gRPC就利用了這個(gè)特點(diǎn)翩概,定義了自用的新幀類型。
第5個(gè)字節(jié)是非常重要的 幀標(biāo)志信息返咱,可以保存8個(gè)標(biāo)志位钥庇,攜帶簡(jiǎn)單的控制信息。常用的標(biāo)志位有 END_HEADERS 表示頭數(shù)據(jù)結(jié)束咖摹,相當(dāng)于HTTP/1 里頭的空行(“\r\n”),END_STREAM 表示單方向數(shù)據(jù)發(fā)送結(jié)束(即EOS,End of Stream),相當(dāng)于HTTP/1里 Chunked 分塊結(jié)束標(biāo)志(“0\r\n\r\n”)
報(bào)文頭里最后4個(gè)字節(jié)是流標(biāo)識(shí)符评姨,也就是幀所屬的流,接收方使用它就可以從亂序的幀里識(shí)別出具有相同流ID的幀序列萤晴,按書(shū)序組裝起來(lái)就實(shí)現(xiàn)了虛擬的“流”

流標(biāo)識(shí)符雖然有4個(gè)字節(jié)吐句,但最高位被保留不用,所以只有31位可以使用店读,也就是說(shuō)嗦枢,流標(biāo)識(shí)符的上限是2^31,大約是21億屯断。

好了文虏,把二進(jìn)制頭理清楚后侣诺,我們來(lái)看一下 Wireshark 抓包的幀實(shí)例:


image.png

在這個(gè)幀里,開(kāi)頭的三個(gè)字節(jié)是"00010a",表示數(shù)據(jù)長(zhǎng)度是266字節(jié)氧秘。
幀類型是1年鸳,表示 HEADERS幀,負(fù)載(payload)里面存放的是被HPACK算法壓縮的頭部信息丸相。

標(biāo)志位是0x25,轉(zhuǎn)換成二進(jìn)制有3個(gè)位被置1
PRIORITY表示設(shè)置了流的優(yōu)先級(jí)阻星,END_HEADERS表示這一個(gè)幀就是完整的頭數(shù)據(jù),END_STREAM表示單方向數(shù)據(jù)發(fā)送結(jié)束已添,后續(xù)再不會(huì)有數(shù)據(jù)幀(即請(qǐng)求報(bào)文完畢,不會(huì)再有DATA幀)

最后4個(gè)字節(jié)的流標(biāo)識(shí)符是整數(shù)1滥酥,表示這個(gè)客戶端發(fā)起的第一個(gè)流更舞,后面的響應(yīng)數(shù)據(jù)幀也會(huì)是這個(gè)ID,也就是說(shuō)在Stream[1]里完成這個(gè)請(qǐng)求響應(yīng)坎吻。

流與多路復(fù)用
流是二進(jìn)制幀的雙向傳輸序列缆蝉。
要搞明白流,關(guān)鍵是要理解幀頭里的流ID.

在HTTP/2 連接上瘦真,雖然幀是亂序收發(fā)的刊头,但只要他們都擁有相同的流ID,就都屬于一個(gè)流,而且在這個(gè)流里幀不是無(wú)需的诸尽,而是有著嚴(yán)格的先后順序

image.png

在概念上原杂,一個(gè)HTTP/2的流就等同于一個(gè)HTTP/1里的“請(qǐng)求-應(yīng)答”。在HTTP/1里一個(gè)“請(qǐng)求-響應(yīng)”報(bào)文來(lái)回是一次HTTP通信您机,在HTTP/2里一個(gè)流也承載了相同的功能穿肄。

你還可以對(duì)照著TCP來(lái)理解。TCP運(yùn)行在IP之上际看,其實(shí)從MAC層咸产、IP層的角度來(lái)看。TCP的“連接”概念也是虛擬的仲闽。但從功能上看脑溢,無(wú)論是HTTP/2的流,還是TCP的連接赖欣,都是實(shí)際存在的屑彻,所以你以后大可不必再糾結(jié)于流的“虛擬”性,把它當(dāng)做是一個(gè)真實(shí)存在的實(shí)體來(lái)理解就好畏鼓。

HTTP/2的流有哪些特點(diǎn)呢酱酬?

  1. 流是可并發(fā)的,一個(gè)HTTP/2連接上可以同時(shí)發(fā)出多個(gè)流傳輸數(shù)據(jù)云矫,也就是并發(fā)多請(qǐng)求膳沽,實(shí)現(xiàn)“多路復(fù)用”;
  2. 客戶端和服務(wù)器都可以創(chuàng)建流,雙方互補(bǔ)干擾挑社;
  3. 流是雙向的陨界,一個(gè)流里面客戶端和服務(wù)器都可以發(fā)送或接收數(shù)據(jù)幀
  4. 流之間沒(méi)有固定關(guān)系,彼此獨(dú)立痛阻,但流內(nèi)部的幀是有嚴(yán)格順序的菌瘪;
  5. 流可以設(shè)置優(yōu)先級(jí),讓服務(wù)器優(yōu)先處理阱当,比如先傳 HTML/CSS,后傳圖片俏扩,優(yōu)化用戶體驗(yàn)
  6. 流ID不能重用,只能順序遞增弊添,客戶端發(fā)起的ID是奇數(shù)录淡,服務(wù)器端發(fā)起的id是偶數(shù);
  7. 在流上發(fā)送“RST_STREAM” 幀可以隨時(shí)終止流,取消接收或發(fā)送油坝;
  8. 第0號(hào)流比較特殊嫉戚,不能關(guān)閉,也不能發(fā)送數(shù)據(jù)幀澈圈,只能發(fā)送控制幀彬檀,用于流量控制。

下面的圖顯示了 連接中無(wú)序的幀是如何依據(jù)流ID重組成流的瞬女。


image.png

HTTP/2 在一個(gè)連接上使用多個(gè)流收發(fā)數(shù)據(jù)窍帝,那么它本身默認(rèn)就會(huì)是長(zhǎng)連接,所以永遠(yuǎn)不需要“Connection”頭字段(Keepalive/close)
又比如诽偷,下載大文件的時(shí)候想取消接收盯桦,在 HTTP/1 里只能斷開(kāi) TCP 連接重新“三次握手”,成本很高渤刃,而在 HTTP/2 里就可以簡(jiǎn)單地發(fā)送一個(gè)“RST_STREAM”中斷流拥峦,而長(zhǎng)連接會(huì)繼續(xù)保持。

再比如卖子,因?yàn)榭蛻舳撕头?wù)器兩端都可以創(chuàng)建流略号,而流 ID 有奇數(shù)偶數(shù)和上限的區(qū)分,所以大多數(shù)的流 ID 都會(huì)是奇數(shù)洋闽,而且客戶端在一個(gè)連接里最多只能發(fā)出 2^30玄柠,也就是 10 億個(gè)請(qǐng)求。

所以就要問(wèn)了:ID 用完了該怎么辦呢诫舅?這個(gè)時(shí)候可以再發(fā)一個(gè)控制幀“GOAWAY”羽利,真正關(guān)閉 TCP 連接。

流狀態(tài)轉(zhuǎn)換
流很重要刊懈,也很復(fù)雜这弧。為了更好地描述運(yùn)行機(jī)制娃闲,HTTP/2 借鑒了 TCP,根據(jù)幀的標(biāo)志位實(shí)現(xiàn)流狀態(tài)轉(zhuǎn)換匾浪。當(dāng)然皇帮,這些狀態(tài)也是虛擬的,只是為了輔助理解蛋辈。

HTTP/2 的流也有一個(gè)狀態(tài)轉(zhuǎn)換圖属拾,雖然比 TCP 要簡(jiǎn)單一點(diǎn),但也不那么好懂冷溶,所以今天我只畫(huà)了一個(gè)簡(jiǎn)化的圖渐白,對(duì)應(yīng)到一個(gè)標(biāo)準(zhǔn)的 HTTP“請(qǐng)求 - 應(yīng)答”。

image.png

最開(kāi)始的時(shí)候流都是“空閑”(idle)狀態(tài)逞频,也就是“不存在”礼预,可以理解成是待分配的“號(hào)段資源”。
當(dāng)客戶端發(fā)送HEADERS幀后虏劲,有了流id,流就進(jìn)入了 **“打開(kāi)” ** 狀態(tài)褒颈,兩端都可以收發(fā)數(shù)據(jù)柒巫,然后客戶端發(fā)送個(gè)“END_STREAM”標(biāo)志位的幀,流就進(jìn)入了“半關(guān)閉”狀態(tài)

這個(gè)“半關(guān)閉”狀態(tài)很重要谷丸,意味著客戶端的請(qǐng)求數(shù)據(jù)以及發(fā)送完了堡掏,需要接受響應(yīng)數(shù)據(jù),而服務(wù)端也知道請(qǐng)求數(shù)據(jù)接收完畢刨疼,之后就要內(nèi)部處理泉唁,再發(fā)送響應(yīng)數(shù)據(jù)。

響應(yīng)數(shù)據(jù)發(fā)完了之后揩慕,也要帶上“END_STREAM”標(biāo)志位亭畜,表示數(shù)據(jù)發(fā)送完畢,這樣流兩端就都進(jìn)入了“關(guān)閉”狀態(tài)迎卤,流就結(jié)束了拴鸵。

剛才也說(shuō)過(guò),流ID不能重用蜗搔,所以流的生命周期就是HTTP/1里的一次完整的“請(qǐng)求-應(yīng)答”劲藐,流關(guān)閉就是一次通信結(jié)束。

下一次在發(fā)送請(qǐng)求就要開(kāi)一個(gè)新流(而不是新連接)樟凄,流id不斷增加聘芜,直到到達(dá)上限,發(fā)送“GOAWAY”幀開(kāi)一個(gè)新的TCP連接缝龄,流ID就又可以重頭計(jì)數(shù)汰现。
你再看看這張圖挂谍,是不是和 HTTP/1 里的標(biāo)準(zhǔn)“請(qǐng)求 - 應(yīng)答”過(guò)程很像,只不過(guò)這是發(fā)生在虛擬的“流”上服鹅,而不是實(shí)際的 TCP 連接凳兵,又因?yàn)榱骺梢圆l(fā),所以 HTTP/2 就可以實(shí)現(xiàn)無(wú)阻塞的多路復(fù)用企软。

小結(jié)

HTTP/2 的內(nèi)容實(shí)在是太多了庐扫,為了方便學(xué)習(xí),我砍掉了一些特性仗哨,比如流的優(yōu)先級(jí)形庭、依賴關(guān)系、流量控制等厌漂。

但只要你掌握了今天的這些內(nèi)容萨醒,以后再看 RFC 文檔都不會(huì)有難度了。

  1. HTTP/2 必須先發(fā)送一個(gè)“連接前言”字符串苇倡,然后才能建立正式連接富纸;
  2. HTTP/2 廢除了起始行,統(tǒng)一使用頭字段旨椒,在兩端維護(hù)"key-value"的索引表晓褪,使用“HPACK”算法壓縮頭部;
  3. HTTP/2 把報(bào)文切分為多種類型的二進(jìn)制幀综慎,報(bào)頭里最重要的字段是流標(biāo)識(shí)符涣仿,標(biāo)記幀屬于哪個(gè)流;
  4. 流是 http/2虛擬的概念示惊,是幀的雙向傳輸序列好港,相當(dāng)與HTTP/1里的一次“請(qǐng)求-應(yīng)答”
  5. 在一個(gè)HTTP/2連接上 可以并發(fā)多個(gè)流,也就是多個(gè)“請(qǐng)求-響應(yīng)” 報(bào)文米罚,這就是多路復(fù)用
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末钧汹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子录择,更是在濱河造成了極大的恐慌崭孤,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糊肠,死亡現(xiàn)場(chǎng)離奇詭異辨宠,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)货裹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門嗤形,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人弧圆,你說(shuō)我怎么就攤上這事赋兵”恃剩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵霹期,是天一觀的道長(zhǎng)叶组。 經(jīng)常有香客問(wèn)我,道長(zhǎng)历造,這世上最難降的妖魔是什么甩十? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮吭产,結(jié)果婚禮上侣监,老公的妹妹穿的比我還像新娘。我一直安慰自己臣淤,他們只是感情好橄霉,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著邑蒋,像睡著了一般姓蜂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上医吊,一...
    開(kāi)封第一講書(shū)人閱讀 49,792評(píng)論 1 290
  • 那天钱慢,我揣著相機(jī)與錄音,去河邊找鬼遮咖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛造虏,可吹牛的內(nèi)容都是我干的御吞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼漓藕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼陶珠!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起享钞,我...
    開(kāi)封第一講書(shū)人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤揍诽,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后栗竖,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體暑脆,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年狐肢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了添吗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡份名,死狀恐怖碟联,靈堂內(nèi)的尸體忽然破棺而出妓美,到底是詐尸還是另有隱情,我是刑警寧澤鲤孵,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布壶栋,位于F島的核電站,受9級(jí)特大地震影響普监,放射性物質(zhì)發(fā)生泄漏贵试。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一鹰椒、第九天 我趴在偏房一處隱蔽的房頂上張望锡移。 院中可真熱鬧,春花似錦漆际、人聲如沸淆珊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)施符。三九已至,卻和暖如春擂找,著一層夾襖步出監(jiān)牢的瞬間戳吝,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工贯涎, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留听哭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓塘雳,卻偏偏與公主長(zhǎng)得像陆盘,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子败明,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348