HTTP前世今生
HTTP 是瀏覽器中最重要且使用最多的協議,是瀏覽器和伺服器之間的通訊語言。隨著瀏覽器的發展,HTTP 為了能適應新的形式也在持續進化。已經歷經0.9,1.0,1.1,2.0等幾個階段, 以及未來的3.0。
開頭先講個小故事,看完你可能滿臉黑人問號,但是彆著急,等你把文章全讀完再回頭品一品這個小故事
我有多幢房子需要還貸。(多狀緩代)
但是突然有人來要錢,我手持水管虛晃了幾個動作就被他按在地上打哭了。(持管虛動安哭)
然後我不服,跟他進行了慢競賽,結果我被他多了一耳光又退縮了 (慢競塞)(多優推縮)
之後我和他對視到眼睛都僵了(隊延僵)
結果他更加狠的多我的臉 (功加多握)
HTTP 0.9
出現時間
1991年
出現原因
用來在網路之間傳遞 HTML 超文字的內容。
實現
採用了基於請求響應的模式,從客戶端發出請求,伺服器返回資料。
流程
- 因為 HTTP 都是基於 TCP 協議的,所以客戶端先要根據 IP 地址、埠和伺服器建立 TCP 連線,而建立連線的過程就是 TCP 協議三次握手的過程。
- 建立好連線之後,會發送一個 GET 請求行的資訊,如GET /index.html用來獲取 index.html。
- 伺服器接收請求資訊之後,讀取對應的 HTML 檔案,並將資料以 ASCII 字元流返回給客戶端。
- HTML 文件傳輸完成後,斷開連線。
圖示
特點
- 只有一個請求行,並沒有 HTTP 請求頭和請求體因為只需要一個請求行就可以完整表達客戶端的需求了。
- 伺服器也沒有返回頭資訊。這是因為伺服器端並不需要告訴客戶端太多資訊,只需要返回資料就可以了。
- 返回的檔案內容是以 ASCII 字元流來傳輸的
HTTP 1.0
出現時間
1994年
出現原因
- 隨著瀏覽器的發展在瀏覽器中展示的不單是 HTML 檔案了,還包括了 JavaScript、CSS、圖片、音訊、視訊等不同型別的檔案。因此需要支援多種型別的檔案下載
- 檔案格式不僅僅侷限於 ASCII 編碼,還有很多其他型別編碼的檔案。
圖示
新增特性:(多狀緩代)
- 對多檔案提供良好的支援,支援多種不同型別的資料。HTTP/1.0 的方案是通過請求頭和響應頭來進行協商,在發起請求時候會通過 HTTP 請求頭告訴伺服器它期待伺服器返回什麼型別的檔案、採取什麼形式的壓縮、提供什麼語言的檔案以及檔案的具體編碼。
accept: text/html // 返回型別 accept-encoding: gzip, deflate, br // 壓縮方式 accept-Charset: ISO-8859-1,utf-8 // 編碼格式 accept-language: zh-CN,zh // 語言
- 引入狀態碼,有的請求伺服器可能無法處理,或者處理出錯,這時候就需要告訴瀏覽器伺服器最終處理該請求的情況,狀態碼是通過響應行的方式來通知瀏覽器的。
- 提供了 Cache 機制,用來快取已經下載過的資料以減輕伺服器的壓力
- 加入了使用者代理的欄位以統計客戶端的基礎資訊,比如 Windows 和 macOS 的使用者數量分別是多少。
記憶:多狀緩代(你有多幢(狀)房子需要還(緩)貸(代))
HTTP 1.1
出現時間
1999年
出現原因
隨著技術的繼續發展,需求也在不斷迭代更新,很快 HTTP/1.0 也不能滿足需求了。
新增特性:
- 改進持久連線。
- 由於http1.0是短連線,所以HTTP/1.0 每進行一次 HTTP 通訊,都需要經歷建立 TCP 連線、傳輸 HTTP 資料和斷開 TCP 連線三個階段。這樣做會增加大量的開銷。為解決這個問題,HTTP/1.1 中增加了持久連線的方法,它的特點是在一個 TCP 連線上可以傳輸多個 HTTP 請求,只要瀏覽器或者伺服器沒有明確斷開連線,那麼該 TCP 連線會一直保持。持久連線在 HTTP/1.1 中是預設開啟的,如不想採用持久連線,可以在 HTTP 請求頭中加上Connection: close。
- 目前瀏覽器中對於同一個域名,預設允許同時建立 6 個 TCP 持久連線。(TODO: 在此新增對比圖)
- 使用 CDN 的實現域名分片機制
- 不成熟的 HTTP 管線化
HTTP/1.1 中的管線化是指將多個 HTTP 請求整批提交給伺服器的技術,雖然可以整批發送請求,不過伺服器依然需要根據請求順序來回復瀏覽器的請求。由於持久連線雖然能減少 TCP 的建立和斷開次數,但是它需要等待前面的請求返回之後,才能進行下一次請求。如果 TCP 通道中的某個請求因為某些原因沒有及時返回,那麼就會阻塞後面的所有請求,這就是著名的隊頭阻塞的問題。HTTP/1.1 試圖用管線化解決隊頭阻塞問題。
- 提供虛擬主機的支援
在 HTTP/1.0 中,每個域名綁定了一個唯一的 IP 地址,因此一個伺服器只能支援一個域名。但是隨著虛擬主機技術的發展,需要實現在一臺物理主機上繫結多個虛擬主機,每個虛擬主機都有自己的單獨的域名,這些單獨的域名都公用同一個 IP 地址。因此,HTTP/1.1 的請求頭中增加了 Host 欄位,用來表示當前的域名地址,這樣伺服器就可以根據不同的 Host 值做不同的處理。
- 對動態生成的內容提供了完美支援
HTTP/1.0 時,需要在響應頭中設定完整的資料大小,如Content-Length: 901,這樣瀏覽器就可以根據設定的資料大小來接收資料。不過隨著伺服器端的技術發展,很多頁面的內容都是動態生成的,因此在傳輸資料之前並不知道最終的資料大小,這就導致了瀏覽器不知道何時會接收完所有的檔案資料。
HTTP/1.1 通過引入 Chunk transfer 機制(分塊傳輸編碼機制)來解決這個問題,伺服器會將資料分割成若干個任意大小的資料塊,每個資料塊傳送時會附上上個數據塊的長度,最後使用一個零長度的塊作為傳送資料完成的標誌。這樣就提供了對動態內容的支援。
- 客戶端 Cookie、安全機制
HTTP/1.1 還引入了客戶端 Cookie 機制和安全機制
記憶:持管虛動安哭,手持水管虛晃了幾個動作就被人安在地上打哭了
HTTP 2.0
出現時間
2015年,大多數主流瀏覽器也於當年年底支援該標準
出現原因
雖然HTTP/1.1 採取了很多優化資源載入速度的策略,也取得了一定的效果,但是 HTTP/1.1對頻寬的利用率卻並不理想。主要是由於以下幾個原因
- TCP 的慢啟動
一旦一個 TCP 連線建立之後,就進入了傳送資料狀態,剛開始 TCP 協議會採用一個非常慢的速度去傳送資料,然後慢慢加快傳送資料的速度,直到傳送資料的速度達到一個理想狀態,我們把這個過程稱為慢啟動。慢啟動是 TCP 為了減少網路擁塞的一種策略,我們是沒有辦法改變的。因為頁面中常用的一些關鍵資原始檔本來就不大,如 HTML 檔案、CSS 檔案和 JavaScript 檔案,通常這些檔案在 TCP 連線建立好之後就要發起請求的,但這個過程是慢啟動,所以耗費的時間比正常的時間要多很多,這樣就增加了首次渲染頁面的時長了。
- 同時開啟了多條 TCP 連線,那麼這些連線會競爭固定的頻寬
系統同時建立了多條 TCP 連線,當頻寬充足時,每條連線傳送或者接收速度會慢慢向上增加,而一旦頻寬不足時,這些 TCP 連線又會減慢傳送或者接收的速度。這樣就會出現一個問題,因為有的 TCP 連線下載的是一些關鍵資源,如 CSS 檔案、JavaScript 檔案等,而有的 TCP 連線下載的是圖片、視訊等普通的資原始檔,但是多條 TCP 連線之間又不能協商讓哪些關鍵資源優先下載,這樣就有可能影響那些關鍵資源的下載速度了。
- HTTP/1.1 隊頭阻塞的問題
在 HTTP/1.1 中使用持久連線時,雖然能公用一個 TCP 管道,但在一個管道中同一時刻只能處理一個請求,在當前的請求沒有結束之前,其他的請求只能處於阻塞狀態。這意味著我們不能隨意在一個管道中傳送請求和接收內容。這是一個很嚴重的問題,因為阻塞請求的因素有很多,並且都是一些不確定性的因素,假如有的請求被阻塞了 5 秒,那麼後續排隊的請求都要延遲等待 5 秒,在這個等待的過程中,頻寬、CPU 都被白白浪費了。並且隊頭阻塞使得資料不能並行請求,所以隊頭阻塞是很不利於瀏覽器優化的。
記憶:慢競塞 慢跑競賽
實現思路
HTTP/2 的思路就是一個域名只使用一個 TCP 長連線來傳輸資料,這樣整個頁面資源的下載過程只需要一次慢啟動,同時也避免了多個 TCP 連線競爭頻寬所帶來的問題。另外隊頭阻塞的問題,等待請求完成後才能去請求下一個資源,這種方式無疑是最慢的,所以 HTTP/2 需要實現資源的並行請求,也就是任何時候都可以將請求傳送給伺服器,而並不需要等待其他請求的完成,然後伺服器也可以隨時返回處理好的請求資源給瀏覽器。即一個域名只使用一個 TCP 長連線和消除隊頭阻塞問題
圖示:
新增特性
- 多路複用,通過引入二進位制分幀層,就實現了 HTTP 的多路複用技術。
- 首先,瀏覽器準備好請求資料,包括了請求行、請求頭等資訊,如果是 POST 方法,那麼還要有請求體
- 這些資料經過二進位制分幀層處理之後,會被轉換為一個個帶有請求 ID 編號的幀,通過協議棧將這些幀傳送給伺服器。
- 伺服器接收到所有幀之後,會將所有相同 ID 的幀合併為一條完整的請求資訊。
- 然後伺服器處理該條請求,並將處理的響應行、響應頭和響應體分別傳送至二進位制分幀層。同樣,二進位制分幀層會將這些響應資料轉換為一個個帶有請求 ID 編號的幀,
- 經過協議棧傳送給瀏覽器。瀏覽器接收到響應幀之後,會根據 ID 編號將幀的資料提交給對應的請求。
- 設定請求的優先順序
我們知道瀏覽器中有些資料是非常重要的,但是在傳送請求時,重要的請求可能會晚於那些不怎麼重要的請求,如果伺服器按照請求的順序來回複數據,那麼這個重要的資料就有可能推遲很久才能送達瀏覽器。為了解決這個問題,HTTP/2 提供了請求優先順序,可以在傳送請求時,標上該請求的優先順序,這樣伺服器接收到請求之後,會優先處理優先順序高的請求。
- 伺服器推送
除了設定請求的優先順序外,HTTP/2 還可以直接將資料提前推送到瀏覽器。
- 頭部壓縮
HTTP/2 對請求頭和響應頭進行了壓縮,你可能覺得一個 HTTP 的標頭檔案沒有多大,壓不壓縮可能關係不大,但你這樣想一下,在瀏覽器傳送請求的時候,基本上都是傳送 HTTP 請求頭,很少有請求體的傳送,通常情況下頁面也有 100 個左右的資源,如果將這 100 個請求頭的資料壓縮為原來的 20%,那麼傳輸效率肯定能得到大幅提升。
記憶: 多優推縮 多了一耳光之後又(優)退(推)縮(縮)了
HTTP 3.0
出現原因
- TCP層面依舊存在隊頭阻塞
在 TCP 傳輸過程中,由於單個數據包的丟失會造成的阻塞。隨著丟包率的增加,HTTP/2 的傳輸效率也會越來越差。有測試資料表明,當系統達到了 2% 的丟包率時,HTTP/1.1 的傳輸效率反而比 HTTP/2 表現得更好。
- TCP 建立連線的延時
TCP 的握手過程也是影響傳輸效率的。我們知道 HTTP/1 和 HTTP/2 都是使用 TCP 協議來傳輸的,而如果使用 HTTPS 的話,還需要使用 TLS 協議進行安全傳輸,而使用 TLS 也需要一個握手過程,這樣就需要有兩個握手延遲過程。總之,在傳輸資料之前,我們需要花掉 3~4 個 RTT,若伺服器相隔較遠,那麼 1 個 RTT 就可能需要 100 毫秒以上了,這種情況下整個握手過程需要 300~400 毫秒,這時使用者就能明顯地感受到“慢”了。
- TCP 協議僵化
中間裝置的僵化:如果我們在客戶端升級了 TCP 協議,但是當新協議的資料包經過這些中間裝置時,它們可能不理解包的內容,於是這些資料就會被丟棄掉。這就是中間裝置僵化,它是阻礙 TCP 更新的一大障礙。
作業系統也是導致 TCP 協議僵化的另外一個原因, 因為 TCP 協議都是通過作業系統核心來實現的,應用程式只能使用不能修改。通常作業系統的更新都滯後於軟體的更新,因此要想自由地更新核心中的 TCP 協議也是非常困難的。
記憶:隊延僵 對視到眼睛都僵了
實現思路
HTTP/3 選擇了一個折衷的方法——UDP 協議,基於 UDP 實現了類似於 TCP 的多路資料流、傳輸可靠性等功能,我們把這套功能稱為 QUIC 協議。
HTTP/2 和 HTTP/3 協議棧
特性
- 實現了類似 TCP 的流量控制、傳輸可靠性的功能
雖然 UDP 不提供可靠性的傳輸,但 QUIC 在 UDP 的基礎之上增加了一層來保證資料可靠性傳輸。它提供了資料包重傳、擁塞控制以及其他一些 TCP 中存在的特性。
- 集成了 TLS 加密功能
目前 QUIC 使用的是 TLS1.3,相較於早期版本 TLS1.3 有更多的優點,其中最重要的一點是減少了握手所花費的 RTT 個數。
- 實現了 HTTP/2 中的多路複用功能
和 TCP 不同,QUIC 實現了在同一物理連線上可以有多個獨立的邏輯資料流。實現了資料流的單獨傳輸,就解決了 TCP 中隊頭阻塞的問題。
- 實現了快速握手功能
由於 QUIC 是基於 UDP 的,所以 QUIC 可以實現使用 0-RTT 或者 1-RTT 來建立連線,這意味著 QUIC 可以用最快的速度來發送和接收資料,這樣可以大大提升首次開啟頁面的速度。
記憶: 功加多握 (更(功)加(加)狠的多(多)我(握)的臉)
面對的問題
- 伺服器和瀏覽器端都沒有對 HTTP/3 提供比較完整的支援
- 系統核心對 UDP 的優化遠遠沒有達到 TCP 的優化程度,這也是阻礙 QUIC 的一個重要原因。
- 中間裝置僵化的問題。這些裝置對 UDP 的優化程度遠遠低於 TCP,據統計使用 QUIC 協議時,大約有 3%~7% 的丟包率。
未來
從標準制定到實踐再到協議優化還需要走很長一段路;並且因為動了底層協議,所以 HTTP/3 的增長會比較緩慢,這和 HTTP/2 有著本質的區別。但是騰訊等公司已經嘗試在生產中落地http3的使用,例如QQ興趣部落。
2020年五月初,微軟宣佈開源自己的內部 QUIC 庫 -- MsQuic,將全面推薦 QUIC 協議替換 TCP/IP 協議。
所以總體來說http3未來可期
參考自:
瀏覽器工作原理與時間 瀏覽器中的網路
https://mp.weixin.qq.com/s/7tKgSmkMWHdIyvrTWJE8sg?scene=25#wechat_redirect
&n