HTTP/2協(xié)議“多路復(fù)用”實(shí)現(xiàn)原理
1.HTTP/2較HTTP/1.1優(yōu)化亮點(diǎn)
HTTP/2是一個(gè)二進(jìn)制協(xié)議,其基于“幀”的結(jié)構(gòu)設(shè)計(jì),改進(jìn)了很多HTTP/1.1痛點(diǎn)問題。下面列舉一些最常被津津樂道的改進(jìn)之處:
多路復(fù)用的流
頭部壓縮
資源優(yōu)先級(jí)和依賴設(shè)置
服務(wù)器推送
流量控制
重置消息
以上列舉的每一項(xiàng)都值得做深入細(xì)致的研究,這里就只針對(duì)“多路復(fù)用”功能的實(shí)現(xiàn)進(jìn)行深入的學(xué)習(xí)。
2.“多路復(fù)用”的原理解析
2.1 什么是多路復(fù)用?
網(wǎng)絡(luò)上有一張圖能清晰的解釋這個(gè)問題:

HTTP/1.1協(xié)議的請(qǐng)求-響應(yīng)模型大家都是熟悉的,我們用“HTTP消息”來表示一個(gè)請(qǐng)求-響應(yīng)的過程,那么HTTP/1.1中的消息是“管道串形化”的:只有等一個(gè)消息完成之后,才能進(jìn)行下一條消息;而HTTP/2中多個(gè)消息交織在了一起,這無疑提高了“通信”的效率。這就是多路復(fù)用:在一個(gè)HTTP的連接上,多路“HTTP消息”同時(shí)工作。
2.2 為什么HTTP/1.1不能實(shí)現(xiàn)“多路復(fù)用”?
簡(jiǎn)單回答就是:HTTP/2是基于二進(jìn)制“幀”的協(xié)議,HTTP/1.1是基于“文本分割”解析的協(xié)議。
看一個(gè)HTTP/1.1簡(jiǎn)單的GET請(qǐng)求例子:
GET / HTTP/1.1
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding:gzip, deflate, br
Accept-Language:zh-CN,zh;q=0.9,en;q=0.8
Cache-Control:max-age=0
Connection:keep-alive
Cookie:imooc_uuid=b2076a1d-6a14-4cd5-91b0-17a9a2461cf4; imooc_isnew_ct=1517447702; imooc_isnew=2; zg_did=%7B%22did%22%3A%20%221662d799f3f17d-0afe8166871b85-454c092b-100200-1662d799f4015b%22%7D; loginstate=1; apsid=Y4ZmEwNGY3OTUwMTdjZTk0ZTc4YzBmYThmMDBmZDYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDEwNzI4OQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5NTMzNjIzNjVAcXEuY29tAAAAAAAAAAAAAAAAAAAAADBmNmM5MzczZTVjMTk3Y2VhMDE2ZjUxNmQ0NDUwY2IxIDPdWyAz3Vs%3DYj; Hm_lvt_fb538fdd5bd62072b6a984ddbc658a16=1541222935,1541224845; Hm_lvt_f0cfcccd7b1393990c78efdeebff3968=1540010199,1541222930,1541234759; zg_f375fe2f71e542a4b890d9a620f9fb32=%7B%22sid%22%3A%201541297212384%2C%22updated%22%3A%201541297753524%2C%22info%22%3A%201541222929083%2C%22superProperty%22%3A%20%22%7B%5C%22%E5%BA%94%E7%94%A8%E5%90%8D%E7%A7%B0%5C%22%3A%20%5C%22%E6%85%95%E8%AF%BE%E7%BD%91%E6%95%B0%E6%8D%AE%E7%BB%9F%E8%AE%A1%5C%22%2C%5C%22%E5%B9%B3%E5%8F%B0%5C%22%3A%20%5C%22web%5C%22%7D%22%2C%22platform%22%3A%20%22%7B%7D%22%2C%22utm%22%3A%20%22%7B%7D%22%2C%22referrerDomain%22%3A%20%22%22%2C%22cuid%22%3A%20%22Jph3DQ809OQ%2C%22%7D; PHPSESSID=h5jn68k1fcaadn61bpoqa9hch2; cvde=5be7a057c314b-1; IMCDNS=1
Host:www.imooc.com
Referer:https://www.imooc.com/
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36以上就是HTTP/1.1發(fā)送請(qǐng)求消息的文本格式:以換行符分割每一條key:value的內(nèi)容,解析這種數(shù)據(jù)用不著什么高科技,相反的,解析這種數(shù)據(jù)往往速度慢且容易出錯(cuò)?!胺?wù)端”需要不斷的讀入字節(jié),直到遇到分隔符(這里指換行符,代碼中可能使用/n或者/r/n表示),這種解析方式是可行的,并且HTTP/1.1已經(jīng)被廣泛使用了二十多年,這事已經(jīng)做過無數(shù)次了,問題一直都是存在的:
一次只能處理一個(gè)請(qǐng)求或響應(yīng),因?yàn)檫@種以分隔符分割消息的數(shù)據(jù),在完成之前不能停止解析。
解析這種數(shù)據(jù)無法預(yù)知需要多少內(nèi)存,這會(huì)帶給“服務(wù)端”很大的壓力,因?yàn)樗恢酪岩恍幸馕龅膬?nèi)容讀到多大的“緩沖區(qū)”中,在保證解析效率和速度的前提下:內(nèi)存該如何分配?
2.3 HTTP/2幀結(jié)構(gòu)設(shè)計(jì)和多路復(fù)用實(shí)現(xiàn)
前邊提到:HTTP/2設(shè)計(jì)是基于“二進(jìn)制幀”進(jìn)行設(shè)計(jì)的,這種設(shè)計(jì)無疑是一種“高超的藝術(shù)”,因?yàn)樗鼘?shí)現(xiàn)了一個(gè)目的:一切可預(yù)知,一切可控。
幀是一個(gè)數(shù)據(jù)單元,實(shí)現(xiàn)了對(duì)消息的封裝。下面是HTTP/2的幀結(jié)構(gòu):

幀的字節(jié)中保存了不同的信息,前9個(gè)字節(jié)對(duì)于每個(gè)幀都是一致的,“服務(wù)器”解析HTTP/2的數(shù)據(jù)幀時(shí)只需要解析這些字節(jié),就能準(zhǔn)確的知道整個(gè)幀期望多少字節(jié)數(shù)來進(jìn)行處理信息。我們先來了解一下幀中每個(gè)字段保存的信息:
| 名稱 | 長(zhǎng)度 | 描述 |
|---|---|---|
| Length | 3 字節(jié) | 表示幀負(fù)載的長(zhǎng)度,默認(rèn)最大幀大小2^14 |
| Type | 1 字節(jié) | 當(dāng)前幀的類型,下面會(huì)做介紹 |
| Flags | 1 字節(jié) | 具體幀的標(biāo)識(shí) |
| R | 1 字節(jié) | 保留位,不需要設(shè)置,否則可能帶來嚴(yán)重后果 |
| Stream Identifier | 31 位 | 每個(gè)流的唯一ID |
| Frame Payload | 不固定 | 真實(shí)幀的長(zhǎng)度,真實(shí)長(zhǎng)度在Length中設(shè)置 |
如果使用HTTP/1.1的話,你需要發(fā)送完上一個(gè)請(qǐng)求,才能發(fā)送下一個(gè);由于HTTP/2是分幀的,請(qǐng)求和響應(yīng)可以交錯(cuò)甚至可以復(fù)用。
為了能夠發(fā)送不同的“數(shù)據(jù)信息”,通過幀數(shù)據(jù)傳遞不同的內(nèi)容,HTTP/2中定義了10種不同類型的幀,在上面表格的Type字段中可對(duì)“幀”類型進(jìn)行設(shè)置。下表是HTTP/2的幀類型:
| 名稱 | ID | 描述 |
|---|---|---|
| DATA | 0x0 | 傳輸流的核心內(nèi)容 |
| HEADERS | 0x1 | 包含HTTP首部,和可選的優(yōu)先級(jí)參數(shù) |
| PRIORITY | 0x2 | 指示或者更改流的優(yōu)先級(jí)和依賴 |
| RST_STREAM | 0x3 | 允許一端停止流(通常是由于錯(cuò)誤導(dǎo)致的) |
| SETTINGS | 0x4 | 協(xié)商連接級(jí)參數(shù) |
| PUSH_PROMISE | 0x5 | 提示客戶端,服務(wù)端要推送些東西 |
| PING | 0x6 | 測(cè)試連接可用性和往返時(shí)延(RTT) |
| GOAWAY | 0x7 | 告訴另外一端,當(dāng)前端已結(jié)束 |
| WINDOW_UPDATE | 0x8 | 協(xié)商一端要接收多少字節(jié)(用于流量控制) |
| CONTINUATION | 0x9 | 用以拓展HEADER數(shù)據(jù)塊 |
有了以上對(duì)HTTP/2幀的了解,我們就可以解釋多路復(fù)用是怎樣實(shí)現(xiàn)的了,不過在這之前我們先來了解“流”的概念:HTTP/2連接上獨(dú)立的、雙向的幀序列交換。流ID(幀首部的6-9字節(jié))用來標(biāo)識(shí)幀所屬的流
下面兩張圖分別表示了HTTP/2協(xié)議上POST請(qǐng)求數(shù)據(jù)流“復(fù)用”的過程,很容易看的明白:

3. 更多特性與簡(jiǎn)介
由于HTTP/2消息中“幀”的設(shè)計(jì),客戶端和服務(wù)端在通信的過程中能夠彼此了解更多的信息。下面再簡(jiǎn)單說一下其他幾點(diǎn)比較重要的特性,算是一個(gè)學(xué)習(xí)引導(dǎo)方向吧。
3.1 流量控制
HTTP/2的新特性之一是基于流的流量控制。不同于HTTP/1.1,只要客戶端可以處理,服務(wù)端就會(huì)盡可能快的發(fā)送數(shù)據(jù),HTTP/2提供了客戶端調(diào)整傳輸速度的能力(服務(wù)端也可以)。WINDOW_UPDATE幀用來完成這件事情,每個(gè)幀告訴對(duì)方,發(fā)送方想要接收多少字節(jié),它將發(fā)送一個(gè)WINDOW_UPDATE幀以指示其更新后的處理字節(jié)能力。
3.2 設(shè)置資源優(yōu)先級(jí)和依賴關(guān)系
流的一個(gè)重要特性是可以設(shè)置優(yōu)先級(jí)和資源數(shù)據(jù)的依賴關(guān)系。HTTP/2通過流的依賴可以實(shí)現(xiàn)這些功能。通過HEADERS幀和PRIORITY幀,客戶端可以明確的告訴服務(wù)端它最需要什么,這是通過聲明依賴關(guān)系和權(quán)重實(shí)現(xiàn)的。
依賴關(guān)系為客戶端提供了一種能力,通過指明某些對(duì)象對(duì)另外一些對(duì)象的依賴,告知服務(wù)器哪些資源應(yīng)該被優(yōu)先傳輸。
權(quán)重讓客戶端告訴服務(wù)器如何具體確定具有共同依賴關(guān)系對(duì)象的優(yōu)先級(jí)。
3.3 服務(wù)端推送
《孫子兵法》中有一句名言:“兵馬未到,糧草先行”。服務(wù)端推送功能就可以實(shí)現(xiàn)這樣一個(gè)功能。當(dāng)頁面還沒有開始請(qǐng)求具體的資源時(shí),服務(wù)端就已經(jīng)把一些資源(像css和js)已經(jīng)推送到客戶端了。當(dāng)瀏覽器要渲染頁面時(shí),資源已經(jīng)在緩存中了,聽起來是一件很酷的事情,實(shí)際上也正是這樣。服務(wù)端推送是通過PUSH_PROMISE幀實(shí)現(xiàn)的,當(dāng)然其實(shí)現(xiàn)的細(xì)節(jié)是非常復(fù)雜的,感興趣的同學(xué)可以研究一下。
HTTP/2的“多路復(fù)用”問題已經(jīng)說明白了,還補(bǔ)充了一些新特性的介紹。當(dāng)然想要深入了解HTTP/2的一些原理,有太多太多的內(nèi)容需要閱讀,實(shí)踐。比如“首部壓縮”算法HPACK是HTTP/2的關(guān)鍵元素之一,是HTTP/2制定開發(fā)組長(zhǎng)時(shí)間的研究成果,其思想內(nèi)容也是特別值得學(xué)習(xí)借鑒的。本節(jié)所設(shè)計(jì)到的東西只是HTTP/2協(xié)議中的冰山一角,RFC7540可以幫助你充分了解該協(xié)議的方方面面。
??愛心三連擊
1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的「點(diǎn)贊,在看」是我創(chuàng)作的動(dòng)力。
2.關(guān)注公眾號(hào)
前端名獅,回復(fù)「1」加入前端交流群,一起學(xué)習(xí)進(jìn)步!3.也可添加微信【qq1248351595】,一起成長(zhǎng)。
“在看轉(zhuǎn)發(fā)”是最大的支持
