6 分鐘了解 HTTP 發(fā)展史
HTTP/0.9HTTP/0.9 是于 1991 年提出的,主要用于學術交流,需求很簡單——用來在網(wǎng)絡之間傳遞 HTML 超文本的內容,所以被稱為超文本傳輸協(xié)議。整體來看,它的實現(xiàn)也很簡單,采用了基于請求響應的模式,從客戶端發(fā)出請求,服務器返回數(shù)據(jù)。
完整請求流程
- 因為 HTTP 都是基于 TCP 協(xié)議的,所以客戶端先要根據(jù) IP 地址、端口和服務器建立 TCP 連接,而建立連接的過程就是 TCP 協(xié)議三次握手的過程。
- 建立好連接之后,會發(fā)送一個 GET 請求行的信息,如GET /index.html 用來獲取 index.html。
- 服務器接收請求信息之后,讀取對應的 HTML 文件,并將數(shù)據(jù)以 ASCII 字符流返回給客戶端。
- HTML 文檔傳輸完成后,斷開連接。
HTTP/0.9 請求過程
特點
- 第一個是只有一個請求行,并沒有 HTTP 請求頭和請求體,因為只需要一個請求行就可以完整表達客戶端的需求了。
- 第二個是服務器也沒有返回頭信息,這是因為服務器端并不需要告訴客戶端太多信息,只需要返回數(shù)據(jù)就可以了。
- 第三個是返回的文件內容是以 ASCII 字符流來傳輸?shù)?,因為都?HTML 格式的文件,所以使用 ASCII 字節(jié)碼來傳輸是最合適的。
HTTP/1.0
HTTP/0.9 存在許多的問題,比如如下的這些:
- 只支持 HTML 類型文件,無法傳輸 JS、CSS、字體、圖片和視頻等類型的文件;
- 文件傳輸格式局限于 ASCII,無法輸出其他類型編碼的文件;
- 只有請求行,傳輸給服務器的信息太少;
- 只響應請求數(shù)據(jù),不能傳輸額外的數(shù)據(jù)給瀏覽器。
所以它已經(jīng)不能滿足當時的需求了,于是乎 HTTP/1.0 來了,它帶來了這些:
- 新增了請求頭和請求體,能傳輸更多的信息給服務器,比如如下請求頭字段:Accept 文件類型,Accept-Encoding 壓縮格式,Accept-Charset 字符編碼格式,Accept-Language 國際化語音:
- Accept: text/html
- Accept-Encoding: gzip, deflate, br
- Accept-Charset: ISO-8859-1,utf-8
- Accept-Language: zh-CN,zh
- 請求頭新增 User-Agent 字段,用于服務器統(tǒng)計客戶端信息:
- User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
- 新增了響應頭,能夠告訴瀏覽器更多的信息,比如 Content-Encoding 表示服務器返回文件的壓縮類型,Content-Type 告訴瀏覽器服務器返回的是什么類型的文件以及使用了什么編碼格式:
- Content-Encoding: gzip
- Content-Type: text/html; charset=utf-8
- 新增響應行狀態(tài)碼,用于告知瀏覽器當前請求的狀態(tài),比如 200 表示請求成功:
- HTTP/1.1 200 OK
- 新增緩存機制,用來緩存已經(jīng)下載過的資源,減輕了服務端壓力。
在構建請求流程上來看,HTTP/1.0 區(qū)別于 HTTP/0.9 最大的區(qū)別就是在請求和響應的時候新增了不少字段用于在瀏覽器和服務器之間通信。
HTTP/1.1
HTTP/1.0 雖說已經(jīng)能夠傳輸不同類型的文件了,但是它還是有缺點的,比如每發(fā)出一次 HTTP 請求都需要經(jīng)歷如下階段:
- 建立 TCP 連接;
- HTTP 請求;
- HTTP 響應;
- 斷開 TCP 連接。
HTTP/1.0 發(fā)送多個同域名請求:
可以發(fā)現(xiàn)每次請求都需要重新建立 TCP 連接和斷開連接的操作,這無疑增加了網(wǎng)絡開銷,同時也延遲了頁面顯示。
HTTP/1.1 在請求頭中增加了 Connection 字段:用于提供 TCP 的持久連接**:
- Connection: keep-alive
它默認是開啟持久連接的,即對于同一個域名,瀏覽器默認支持 6 個 TCP 持久連接。當啟用持久連接后,多個同域名下的請求發(fā)送會是如下情況:
HTTP/1.1 中新增 Host 字段,用于支持虛擬主機
- Host: bubuzou.com
- “虛擬主機:一臺物理機器上綁定多個虛擬主機,每個虛擬主機有單獨的域名,這些域名都公用一個 IP 地址。
HTTP/1.1 通過引入 Chunk transfer 機制來支持動態(tài)內容:服務器會將數(shù)據(jù)分割成若干個任意大小的數(shù)據(jù)塊,每個數(shù)據(jù)塊發(fā)送時會附上上個數(shù)據(jù)塊的長度,最后使用一個零長度的塊作為發(fā)送數(shù)據(jù)完成的標志。
HTTP/1.1 還引入了客戶端 Cookie 機制和安全機制
HTTP/2
我們知道 HTTP/1.1 為網(wǎng)絡效率做了大量的優(yōu)化,最核心的有如下三種方式:
- 增加了持久連接;
- 瀏覽器為每個域名最多同時維護 6 個 TCP 持久連接;
- 使用 CDN 的實現(xiàn)域名分片機制。
HTTP/1.1 中依然存在的問題
雖然 HTTP/1.1 采取了很多優(yōu)化資源加載速度的策略,也取得了一定的效果,但是 HTTP/1.1對帶寬的利用率卻并不理想,這也是 HTTP/1.1 的一個核心問題。
帶寬是指每秒最大能發(fā)送或者接收的字節(jié)數(shù)。我們把每秒能發(fā)送的最大字節(jié)數(shù)稱為上行帶寬,每秒能夠接收的最大字節(jié)數(shù)稱為下行帶寬。
之所以說 HTTP/1.1 對帶寬的利用率不理想,是因為 HTTP/1.1 很難將帶寬用滿。比如我們常說的 100M 帶寬,實際的下載速度能達到 12.5M/S,而采用 HTTP/1.1 時,也許在加載頁面資源時最大只能使用到 2.5M/S,很難將 12.5M 全部用滿。
之所以會出現(xiàn)這個問題,主要是 3 個問題導致的:
第一個原因,TCP 的慢啟動
一旦一個 TCP 連接建立之后,就進入了發(fā)送數(shù)據(jù)狀態(tài),剛開始 TCP 協(xié)議會采用一個非常慢的速度去發(fā)送數(shù)據(jù),然后慢慢加快發(fā)送數(shù)據(jù)的速度,直到發(fā)送數(shù)據(jù)的速度達到一個理想狀態(tài),我們把這個過程稱為慢啟動。這個過程可以想象是一輛車的啟動過程,開始的時候慢,當速度起來后加速就更快了。
而之所以說慢啟動會帶來性能問題,是因為頁面中常用的一些關鍵資源文件本來就不大,如 HTML 文件、CSS 文件和 JavaScript 文件,通常這些文件在 TCP 連接建立好之后就要發(fā)起請求的,但這個過程是慢啟動,所以耗費的時間比正常的時間要多很多,這樣就推遲了寶貴的首次渲染頁面的時長了。
第二個原因,同時開啟了多條 TCP 連接,那么這些連接會競爭固定的帶寬
你可以想象一下,系統(tǒng)同時建立了多條 TCP 連接,當帶寬充足時,每條連接發(fā)送或者接收速度會慢慢向上增加;而一旦帶寬不足時,這些 TCP 連接又會減慢發(fā)送或者接收的速度。
這樣就會出現(xiàn)一個問題,因為有的 TCP 連接下載的是一些關鍵資源,如 CSS 文件、JavaScript 文件等,而有的 TCP 連接下載的是圖片、視頻等普通的資源文件,但是多條 TCP 連接之間又不能協(xié)商讓哪些關鍵資源優(yōu)先下載,這樣就有可能影響那些關鍵資源的下載速度了。
第三個原因,HTTP/1.1 隊頭阻塞的問題
我們知道在 HTTP/1.1 中使用持久連接時,雖然能公用一個 TCP 管道,但是在一個管道中同一時刻只能處理一個請求,在當前的請求沒有結束之前,其他的請求只能處于阻塞狀態(tài)。這意味著我們不能隨意在一個管道中發(fā)送請求和接收內容。
這是一個很嚴重的問題,因為阻塞請求的因素有很多,并且都是一些不確定性的因素,假如有的請求被阻塞了 5 秒,那么后續(xù)排隊的請求都要延遲等待 5 秒,在這個等待的過程中,帶寬、CPU 都被白白浪費了。
HTTP/2 的多路復用
為了解決 HTTP/1.1 中存在的問題,在 HTTP/2 中采用最具顛覆性的方案:多路復用機制。

HTTP/2 多路復用是什么
HTTP/2 的多路復用機制用簡單的話來說就是瀏覽器針對同一域名的資源,只建立一個 TCP 連接通道,所有的針對這個域名的請求全部都在這個通道中完成;
除此之外,數(shù)據(jù)的傳輸不再使用文本格式,而是會將它們分割為更小的流和幀,并對他們采用二進制格式的編碼。在一個 TCP 連接通道中,支持任意數(shù)量的雙向數(shù)據(jù)流,這些數(shù)據(jù)流是并行、亂序的且它們之間互不干擾。而數(shù)據(jù)流中傳輸?shù)臄?shù)據(jù)是二進制幀,它是 HTTP/2 中數(shù)據(jù)傳輸?shù)淖钚挝?,一個流中的幀是按照順序傳輸?shù)模沂遣⑿械?,所以無需按順序等待。
解決了什么問題
因為只使用一個 TCP 連接,所以減少了由于 TCP 慢啟動而消耗的時間,另外也由于只有單條 TCP 連接,所以不存在不同的 TCP 爭奪網(wǎng)絡帶寬的問題。
客戶端發(fā)送的請求經(jīng)過二進制分幀層后,不再是一個個完整的 HTTP 請求報文,而是一堆亂序的幀(即不同流的幀是亂的,但是同一條流的幀數(shù)順序傳輸?shù)?,所以就不會按順序傳輸,也就不存在等待,從而解決了 HTTP 對頭阻塞問題。
是如何實現(xiàn)的
- 首先,瀏覽器準備好請求數(shù)據(jù),包括了請求行、請求頭等信息,如果是 POST 方法,那么還要有請求體。
- 這些數(shù)據(jù)經(jīng)過二進制分幀層處理之后,會被轉換為一個個帶有請求 ID 編號的幀,通過協(xié)議棧將這些幀發(fā)送給服務器。請求頭的信息存在 header 幀中,而請求體數(shù)據(jù)存在 data 幀中。
- 服務器接收到所有幀之后,會將所有相同 ID 的幀合并為一條完整的請求信息。
- 然后服務器處理該條請求,并將處理的響應行、響應頭和響應體分別發(fā)送至二進制分幀層。
- 同樣,二進制分幀層會將這些響應數(shù)據(jù)轉換為一個個帶有請求 ID 編號的幀,經(jīng)過協(xié)議棧發(fā)送給瀏覽器。
- 瀏覽器接收到響應幀之后,會根據(jù) ID 編號將幀的數(shù)據(jù)提交給對應的請求。
HTTP/2 其他特性
1. 可以設置請求的優(yōu)先級
在瀏覽器中,某些數(shù)據(jù)是非常重要的,比如關鍵 CSS 或者 JS,這些重要的數(shù)據(jù)如果比較晚才推送到瀏覽器,那么對用戶來說肯定是一個不好的體驗。
所以 HTTP/2 中可以支持設置請求的優(yōu)先級,這樣服務器收到高優(yōu)先級的請求后,會優(yōu)先處理。
2. 服務器推送
在 HTTP/2 中服務器解析到一個 HTML 頁面后,服務器知道瀏覽器需要這個頁面上引用到的資源,比如 CSS 和 JS,那么服務器就會主動的把這些資源一并推送給瀏覽器,減少客戶端的等待時間。
3. 頭部壓縮
HTTP/2 使用 HPACK 壓縮算法對請求頭和響應頭進行壓縮,雖然單個請求壓縮之后效果不是很明顯,但是如果一個頁面有 100 個請求,那每個請求壓縮 20% 之后,那提速效果就很明顯了。
而 HPACK 的壓縮原理其實就是 2 點:
- 它要求客戶端和服務器兩者都維護和更新先前看到的報頭字段的索引列表(即,建立共享的壓縮上下文),然后將該列表用作有效編碼先前傳輸?shù)闹档膮⒖?。在實際傳輸?shù)臅r候用索引代替每一側的靜態(tài)或動態(tài)表中已經(jīng)存在的字段,從而減小每個請求的大小。
- 它允許通過靜態(tài)霍夫曼碼對發(fā)送的標頭字段進行編碼,從而減小了它們各自的傳輸大小。
HTTP/3
HTTP/2 依然是基于 TCP 的,所以還存在以下一些問題。
TCP 的隊頭阻塞
HTTP/2 中多個請求是跑在一個 TCP 連接中的,如果某個數(shù)據(jù)流中出現(xiàn)了丟包的情況,就會阻塞該 TCP 連接中的所有請求。這個和 HTTP/1.1 中的不同,在 HTTP/1.1 中,由于瀏覽器為每個域名建立了 6 個 TCP 連接,如果其中一個 TCP 連接發(fā)生了隊頭阻塞,那么其他的 5 個連接依然可以繼續(xù)傳輸數(shù)據(jù)。
TCP 建立連接的延時
在傳輸數(shù)據(jù)之前,需要進行 TCP 的 3 次握手,需要花費 1.5 個 RTT;如果是 HTTPS,那還需要進行 TLS 連接,又需要 1 ~ 2 個 RTT。
- “網(wǎng)絡延遲又叫 RTT(Round Trip Time),是從瀏覽器發(fā)送一個數(shù)據(jù)包到服務器,再從服務器返回數(shù)據(jù)包到瀏覽器的整個往返時間。
總之,在傳輸數(shù)據(jù)之前需要花掉 3 ~ 4 個 RTT。如果客戶端和服務器距離近的話,那 1 個 RTT 大概是 10ms,但如果遠的話,可能是 100ms,所以傳輸數(shù)據(jù)之前需要花掉 300ms 左右,這個時候就能感覺到慢了。
TCP 協(xié)議僵化
我們知道 TCP 協(xié)議存在隊頭阻塞和建立連接延遲的問題,但是又沒辦法改進 TCP 協(xié)議,理由有如下 2 個:
中間設備僵化。中間設備比如路由器、交換機、防火墻和 NAT 等,這些設備依賴的軟件使用了大量的 TCP 特性,一旦功能被設置后就很少進行更新了。如果在客戶端進行升級 TCP 協(xié)議,那么當新協(xié)議的數(shù)據(jù)包經(jīng)過這些設備的時候,可能會不理解包的內容,造成數(shù)據(jù)丟失。
操作系統(tǒng)也是導致 TCP 協(xié)議僵化的另外一個原因。
QUIC 協(xié)議

HTTP/3 是基于 UDP 實現(xiàn)的,實現(xiàn)了類似于 TCP 的多路數(shù)據(jù)流、傳輸可靠性等功能,我們把這套功能稱為 QUIC 協(xié)議。
- 實現(xiàn)了類似 TCP 的流量控制、傳輸可靠性的功能。雖然 UDP 不提供可靠性的傳輸,但 QUIC 在 UDP 的基礎之上增加了一層來保證數(shù)據(jù)可靠性傳輸。它提供了數(shù)據(jù)包重傳、擁塞控制以及其他一些 TCP 中存在的特性。
- 集成了 TLS 加密功能。目前 QUIC 使用的是 TLS1.3,相較于早期版本 TLS1.3 有更多的優(yōu)點,其中最重要的一點是減少了握手所花費的 RTT 個數(shù)。
- 實現(xiàn)了 HTTP/2 中的多路復用功能。和 TCP 不同,QUIC 實現(xiàn)了在同一物理連接上可以有多個獨立的邏輯數(shù)據(jù)流(如下圖)。實現(xiàn)了數(shù)據(jù)流的單獨傳輸,就解決了 TCP 中隊頭阻塞的問題。
- 實現(xiàn)了快速握手功能。由于 QUIC 是基于 UDP 的,所以 QUIC 可以實現(xiàn)使用 0-RTT 或者 1-RTT 來建立連接,這意味著 QUIC 可以用最快的速度來發(fā)送和接收數(shù)據(jù),這樣可以大大提升首次打開頁面的速度。
HTTP/3 的挑戰(zhàn)
- 第一,從目前的情況來看,服務器和瀏覽器端都沒有對 HTTP/3 提供比較完整的支持。Chrome 雖然在數(shù)年前就開始支持 Google 版本的 QUIC,但是這個版本的 QUIC 和官方的 QUIC 存在著非常大的差異。
- 第二,部署 HTTP/3 也存在著非常大的問題。因為系統(tǒng)內核對 UDP 的優(yōu)化遠遠沒有達到 TCP 的優(yōu)化程度,這也是阻礙 QUIC 的一個重要原因。
- 第三,中間設備僵化的問題。這些設備對 UDP 的優(yōu)化程度遠遠低于 TCP,據(jù)統(tǒng)計使用 QUIC 協(xié)議時,大約有 3%~7% 的丟包率。