當咱們打開網站時也許不會去留意網站前面的HTTP是怎麼來的。可是它毫無疑問在網絡中有着舉足輕重的地位。本文從起源到發展,詳說HTTP從1到3的演變。html
本文不致力於講完 HTTP 的所有內容,事實上短短的篇幅也不可能講完。本文也無心於深挖 HTTP 中的某一點,這是像 《HTTP 權威指南》或者是 RFC 協議作的事。算法
本文目標是幫助讀者理清 HTTP 的演化過程,說說 HTTP 變化的那些事。瀏覽器
HTTP 最初是 Tim BernersLee 1989 年在歐洲核子研究組織(CERN)所發起的。Tim BernersLee 提出了一種能讓遠隔兩地的研究者們共享知識的設想。這個設想的基本理念是:藉助多文檔之間相互關聯造成的超文本(HyperText),連成可相互參閱的 WWW(World Wide Web,萬維網)。用於傳輸的超文本傳輸協議(HyperText Transfer Protocol),即 HTTP 由此誕生。緩存
WWW 這一名稱,是 Web 瀏覽器當年用來瀏覽超文本的客戶端應用程序時的名稱。如今則用來表示這一系列的集合,也可簡稱爲 Web。安全
HTTP 自己是一個簡單的請求-響應協議,它一般運行在 TCP 之上。從整個網絡模型來看,HTTP 是應用層的一個協議。在 OSI 七層模型中,HTTP 位於最上層。它並不涉及數據包的傳輸,只是規定了客戶端和服務器之間的通訊格式。定了客戶端可能發送給服務器什麼樣的消息以及獲得什麼樣的響應。請求和響應消息的頭以 ASCII 碼形式給出。服務器
HTTP 採用 BS 架構,也就是瀏覽器到服務器的架構,客戶端經過瀏覽器發送 HTTP 請求給服務器,服務器通過解析響應客戶端的請求。就是這個簡單實用的模型,使得 HTTP 這個基於 TCP/IP 的協議迅速推廣。網絡
HTTP 的演化並非一蹴而就的。當年 HTTP 的出現主要是爲了解決文本傳輸的難題。因爲協議自己很是簡單,因而在此基礎上設想了不少應用方法並投入了實際使用。如今 HTTP 已經超出了 Web 這個框架的侷限,被運用到了各類場景裏。架構
HTTP/0.9併發
HTTP 協議最先的一個版本是 1990 年發佈的 HTTP/0.9。框架
前面說到,HTTP 於 1989 年問世。那時的 HTTP 並無做爲正式的標準被創建。這時的 HTTP 其實含有 HTTP/1.0 以前版本的意思,所以被稱爲 HTTP/0.9。這個版本只有一個命令:GET。經過 GET 能夠獲取服務器的資源,好比請求服務器根目錄下的 index.html 文件。這個版本的協議規定,服務器只能迴應 HTML 格式的字符串,不能迴應其它格式,也就是說圖像、視頻等多媒體資源,在 HTTP/0.9 這個版本上是沒法進行傳輸的。
HTTP/1.0
HTTP 正式做爲標準被公佈是在 1996 年的 5 月,版本被命名爲 HTTP/1.0,並記載於 RFC1945 [www.ietf.org/rfc/rfc1945…]。雖然說是初期標準,但該協議標準至今仍被普遍使用在服務器端。
HTTP/1.0 版本發佈,增長了 POST 命令和 HEAD 命令,豐富了瀏覽器與服務器的互動手段。這個版本的 HTTP 協議能夠發送任何格式的內容,包括傳輸文字、圖像、視頻、文件等,這爲互聯網的大發展奠基了基礎。
HTTP/1.0 除了增長了請求方法以及對發送文件的支持以外,還增長了格式的改變。除了數據部分,每次通訊都必須包括頭信息(HTTP header),用來描述一些元數據。另外還增長了狀態碼、多字符集支持、多部分發送(multi-part type)、權限(authorization)、緩存(cache)、內容編碼(content encoding)等等。
HTTP/1.1
HTTP/1.0 版也並非完美的,它的主要缺點是,每一次創建 TCP 鏈接只能發送一個請求。發送數據完畢,鏈接就關閉,若是還要請求其餘資源,就必須再新建一個鏈接。若是屢次請求,勢必就會對服務器產生較大的資源性能損耗。
1997 年 1 月公佈的 HTTP/1.1 是目前主流的 HTTP 協議版本。當初的標準是 RFC2068,以後發佈的修訂版 RFC2616 就是當前的最新版本。
其中最著名的是 1999 年 6 月公佈的 RFC 2616 [tools.ietf.org/html/rfc261…],定義了 HTTP 協議中現今普遍使用的一個版本——HTTP/1.1。
這個版本最大的變化就是將持久化鏈接加入了 HTTP 標準,即 TCP 鏈接默認不關閉,能夠被多個請求複用。此外,HTTP/1.1 版還新增了許多方法,例如:PUT、PATCH、HEAD、OPTIONS、DELETE。獲得進一步完善的HTTP/1.1 版本,一直沿用至今。
請求
客戶端發送一個 HTTP 請求到服務器,請求消息包括如下格式:
請求行(request line)、請求頭部(header)、空行和請求數據四個部分組成。
Get 請求例子
1 > GET / HTTP/1.1 2 > Host: www.baidu.com 3 > User-Agent: curl/7.52.1 4 > Accept: /
第一部分:請求行,用來講明請求類型,要訪問的資源以及所使用的 HTTP 版本。
第二部分:請求頭部,緊接着請求行(即第一行)以後的部分,用來講明服務器要使用的附加信息
從第二行起爲請求頭部,HOST 將指出請求的目的地。User-Agent,服務器端和客戶端腳本都能訪問它,它是瀏覽器類型檢測邏輯的重要基礎。該信息由你的瀏覽器來定義,而且在每一個請求中自動發送等等。
第三部分:空行,請求頭部後面的空行是必須的
即便第四部分的請求數據爲空,也必須有空行。
第四部分:請求數據也叫主體,能夠添加任意的其餘數據。
這個例子的請求數據爲空。
響應消息
通常狀況下,服務器接收並處理客戶端發過來的請求後,會返回一個 HTTP 的響應消息。
HTTP 響應也由四個部分組成,分別是:狀態行、消息報頭、空行和響應正文。
例子
1 < HTTP/1.1 200 OK 2 < Accept-Ranges: bytes 3 < Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform 4 < Connection: keep-alive 5 < Content-Length: 2381 6 < Content-Type: text/html 7 < Date: Thu, 11 Jun 2020 16:04:33 GMT 8 < Etag: "588604c8-94d" 9 < Last-Modified: Mon, 23 Jan 2017 13:27:36 GMT 10 < Pragma: no-cache 11 < Server: bfe/1.0.8.18 12 < Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/ 13 < 14 15 <meta HTTP-equiv=content-type content=text/html;charset=utf-8><meta HTTP-equiv=X-UA-Compatible content=IE=Edge>... 16
第一部分:狀態行,由 HTTP 協議版本號、狀態碼、狀態消息三部分組成。
第一行爲狀態行,(HTTP/1.1)代表 HTTP 版本爲 1.1 版本,狀態碼爲 200,狀態消息爲(ok)
第二部分:消息報頭,用來講明客戶端要使用的一些附加信息
第二行和第三行爲消息報頭。
Date:生成響應的日期和時間;Content-Type:指定了 MIME 類型的 HTML(text/html),編碼類型是 UTF-8
第三部分:空行,消息報頭後面的空行是必須的
第四部分:響應正文,服務器返回給客戶端的文本信息。
空行後面的 HTML 部分爲響應正文。
狀態碼
狀態代碼有三位數字組成,第一個數字定義了響應的類別,共分五種類別:
1xx:指示信息–表示請求已接收,繼續處理
2xx:成功–表示請求已被成功接收、理解、接受
3xx:重定向–要完成請求必須進行更進一步的操做
4xx:客戶端錯誤–請求有語法錯誤或請求沒法實現
5xx:服務器端錯誤–服務器未能實現合法的請求
HTTP 的誕生是爲了解決信息傳遞和共享的問題,並無考慮到互聯網高速發展後面臨的安全問題。
通常來講 HTTP 從 TCP 三次握手後,便開始了數據傳輸。因爲 HTTP 自己以明文形式來傳輸數據,並不具有任何數據加密、身份校驗的機制。同時下層協議並不對數據安全性、保密性提供保證。因此在網絡傳輸的過程當中,任意節點的第三方均可以隨意劫持流量、篡改數據或竊取信息。
HTTP 沒法確保數據的保密性、完整性和真實性,已經不能適應現代互聯網應用的安全需求。
隨着 Web 的日益壯大,HTTP 的使用呈鉅額增加趨勢,對信息安全的需求也越來越迫切,SSL(Secure SocketsLayer ,安全套接層)應運而生。
當對於安全需求,首先想到的就是對信息進行加密。SSL ,安全套接層,顧名思義是在 TCP 上提供的安全套接字層。其位於應用層和傳輸層之間,應用層數據再也不直接傳遞給傳輸層而是傳遞給 SSL 層,SSL 層對從應用層收到的數據進行加密,利用數據加密、身份驗證和消息完整性驗證機制,爲網絡上數據的傳輸提供安全性保證。HTTPS 即是指 Hyper Text Transfer Protocol over SecureSocket Layer。
談到具體實施上,業內一般採用的通常有對稱加密和非對稱加密。採用何種方式進行加密?如何判斷服務器未被篡改?如何傳遞加密密鑰?帶着這樣的問題,咱們來看看 HTTPS 的工做流程。
一、客戶端發起 HTTPS 請求
這個沒什麼好說的,就是用戶在瀏覽器裏輸入一個 HTTPS 網址,而後鏈接到 server 的 443 端口。
二、服務端的配置
採用 HTTPS 協議的服務器必需要有一套數字證書,能夠本身製做,也能夠向組織申請,區別就是本身頒發的證書須要客戶端驗證經過,才能夠繼續訪問,而使用受信任的公司申請的證書則不會彈出提示頁面(Let‘s Encrypt 就是個不錯的選擇,免費的 SSL 證書)。
這套證書其實就是一對公鑰和私鑰,若是對公鑰和私鑰不太理解,能夠想象成一把鑰匙和一個鎖頭,只是全世界只有你一我的有這把鑰匙,你能夠把鎖頭給別人,別人能夠用這個鎖把重要的東西鎖起來,而後發給你,由於只有你一我的有這把鑰匙,因此只有你才能看到被這把鎖鎖起來的東西。
三、傳送證書
這個證書其實就是公鑰,只是包含了不少信息,如證書的頒發機構,過時時間等等。
四、客戶端解析證書
這部分工做是有客戶端的 TLS 來完成的,首先會驗證公鑰是否有效,好比頒發機構,過時時間等等,若是發現異常,則會彈出一個警告框,提示證書存在問題。
若是證書沒有問題,那麼就生成一個隨機值,而後用證書對該隨機值進行加密,就好像上面說的,把隨機值用鎖頭鎖起來,這樣除非有鑰匙,否則看不到被鎖住的內容。
五、傳送加密信息
這部分傳送的是用證書加密後的隨機值,目的就是讓服務端獲得這個隨機值,之後客戶端和服務端的通訊就能夠經過這個隨機值來進行加密解密了。
六、服務段解密信息
服務端用私鑰解密後,獲得了客戶端傳過來的隨機值(私鑰),而後把內容經過該值進行對稱加密,所謂對稱加密就是,將信息和私鑰經過某種算法混合在一塊兒,這樣除非知道私鑰,否則沒法獲取內容,而正好客戶端和服務端都知道這個私鑰,因此只要加密算法夠彪悍,私鑰夠複雜,數據就夠安全。
七、傳輸加密後的信息
這部分信息是服務段用私鑰加密後的信息,能夠在客戶端被還原。
八、客戶端解密信息
客戶端用以前生成的私鑰解密服務段傳過來的信息,因而獲取瞭解密後的內容,整個過程第三方即便監聽到了數據,也一籌莫展。
簡單說完了 HTTPS 的工做流程。讓咱們再將注意力放在 SSL 的演化上。
1994年,Netscape 建立了 SSL 協議的原始規範並逐步發佈協議改進版本。1995 年發佈 SSL 2.0。1996年,Netscape 和 Paul Kocher 共同設計發佈 SSL 3.0 協議,得到互聯網普遍承認和支持。因特網工程任務組(IETF)接手負責該協議,並將其重命名爲 TLS(傳輸層安全)協議。
咱們看到,SSL 2.0 規範是在 1995 年左右發佈的,而 SSL 3.0 是在 1996 年 11 月發佈的。有趣的是,SSL 3.0 是在 RFC 6101 [tools.ietf.org/html/rfc610…] 中描述的,該 RFC 於 2011 年 8 月發佈。它位於歷史類別中,該類別一般是被考慮和被丟棄的文檔想法,或者是在決定記錄它們時已經具備歷史意義的協議(根據 IETF [www.ietf.org/about/group…] 說明)。在這種狀況下,有一個描述 SSL 3.0 的 IETF 文檔是頗有必要的,由於在其能夠被用做規範參考。
再來看看,SSL 是如何激發 TLS 的發展的。後者在 1996 年 11 月以 draft-ietf-tls-protocol-00 [tools.ietf.org/html/draft-…] 宣告開始。它經歷了六個草案版本,並於 1999 年初做爲 RFC 2246 [tools.ietf.org/html/rfc224…] - TLS 1.0 正式發佈。
在 1995 和 1999 年間,SSL 和 TLS 協議用於保護互聯網上的 HTTP 通訊。這做爲事實上的標準運行良好。直到 1998 年 1 月,隨着 I-D draft-ietf-tls-HTTPs-00 [tools.ietf.org/html/draft-…] 的發佈,HTTPS 的正式標準化過程纔開始。該工做於 2000 年 5 月以 RFC 2616 - HTTP 上的 TLS 的發佈結束。
TLS 在 2000 到 2007 年間繼續發展,標準化爲 TLS 1.1 和 1.2。直至七年後,TLS 的下一個版本開始進行,該版本在 2014 年四月被採納爲 draft-ietf-tls-tls13-00 [tools.ietf.org/html/draft-…],並在 28 份草稿後,於 2018 年八月出了完成版本 RFC 8446 [tools.ietf.org/html/rfc844…] - TLS 1.3。
回到 HTTP 自己。在很長一段時間裏,HTTP/1.1 已經足夠好了(確實是,如今仍應用最爲普遍),可是,Web 不斷變化的需求使得咱們須要一個更好更合適的協議。
HTTP/1.1 自從 1997 年發佈以來,咱們已經使用 HTTP/1.x 至關長一段時間了。但隨着互聯網近十年爆炸式的發展,從當初網頁內容以文本爲主,到如今以富媒體(如圖片、聲音、視頻)爲主,並且對頁面內容實時性高要求的應用愈來愈多(好比聊天、視頻直播),因此當時協議規定的某些特性,已經逐漸沒法知足現代網絡的需求了。
若是你有仔細觀察,那些最流行的網站首頁所須要下載資源的話,會發現一個很是明顯的趨勢。近年來加載網站首頁須要下載的數據量在逐漸增長,並已經超過了 2100K。但在這裏咱們更關心的是:平均每一個頁面爲了完成顯示與渲染所須要下載的資源數也已經超過了 100 個。
基於此,在 2010 年到 2015 年,谷歌經過實踐一個實驗性的 SPDY 協議,證實了一個在客戶端和服務器端交換數據的另類方式。其收集了瀏覽器和服務器端的開發者的焦點問題,明確了響應數量的增長和解決複雜的數據傳輸。在啓動 SPDY 這個項目時預設的目標是:
頁面加載時間 (PLT) 減小 50%。
無需網站做者修改任何內容。
將部署複雜性降至最低,無需變動網絡基礎設施。
與開源社區合做開發這個新協議。
收集真實性能數據,驗證這個實驗性協議是否有效。爲了達到下降目標,減小頁面加載時間的目標,SPDY 引入了一個新的二進制分幀數據層,以實現多向請求和響應、優先次序、最小化及消除沒必要要的網絡延遲,目的是更有效地利用底層 TCP 鏈接。
**HTTP/1.1 有兩個主要的缺點:安全不足和性能不高,**因爲揹負着 HTTP/1.x 龐大的歷史包袱,因此協議的修改,兼容性是首要考慮的目標,不然就會破壞互聯網上無數現有的資產。
而如上圖所示,SPDY 位於 HTTP 之下,TCP 和 SSL 之上,這樣能夠輕鬆兼容老版本的 HTTP 協議同時能夠使用已有的 SSL 功能。
SPDY 協議在 Chrome 瀏覽器上證實可行之後,就被看成 HTTP/2 的基礎,主要特性都在 HTTP/2 之中獲得繼承。
因而時間來到 2015 年,HTTP/2.0 問世。
HTTP/2 相比 HTTP/1.1 的修改並不會破壞現有程序的工做,可是新的程序能夠藉由新特性獲得更好的速度。
HTTP/2 保留了 HTTP/1.1 的大部分語義,例如請求方法、狀態碼、乃至 URI 和絕大多數 HTTP 頭部字段一致。而 HTTP/2 採用了新的方法來編碼、傳輸客戶端和服務器間的數據。
來看看 HTTP/2 的具體特色:
二進制分幀層:在應用層與傳輸層之間增長一個二進制分幀層,以此達到在不改動 HTTP 的語義,HTTP 方法、狀態碼、URI 及首部字段的狀況下,突破 HTTP/1.1 的性能限制,改進傳輸性能,實現低延遲和高吞吐量。在二進制分幀層上,HTTP/2.0 會將全部傳輸的信息分割爲更小的消息和幀,並對它們採用二進制格式的編碼,其中 HTTP1.x 的首部信息會被封裝到 Headers 幀,而咱們的 request body 則封裝到 Data 幀裏面。
多路複用:對於 HTTP/1.x,即便開啓了長鏈接,請求的發送也是串行發送的,在帶寬足夠的狀況下,對帶寬的利用率不夠,HTTP/2.0 採用了多路複用的方式,能夠並行發送多個請求,提升對帶寬的利用率。
數據流優先級:因爲請求能夠併發發送了,那麼若是出現了瀏覽器在等待關鍵的 CSS 或者 JS 文件完成對頁面的渲染時,服務器卻在專一的發送圖片資源的狀況怎麼辦呢?HTTP/2.0 對數據流能夠設置優先值,這個優先值決定了客戶端和服務端處理不一樣的流採用不一樣的優先級策略。
服務端推送:在 HTTP/2.0 中,服務器能夠向客戶發送請求以外的內容,好比正在請求一個頁面時,服務器會把頁面相關的 logo,CSS 等文件直接推送到客戶端,而不會等到請求來的時候再發送,由於服務器認爲客戶端會用到這些東西。這至關於在一個 HTML 文檔內集合了全部的資源。
頭部壓縮:使用首部表來跟蹤和存儲以前發送的鍵值對,對於相同的內容,不會再每次請求和響應時發送。
HTTP/2.0 支持明文 HTTP 傳輸,而 SPDY 強制使用 HTTPS。
HTTP/2.0 消息頭的壓縮算法採用 HPACK,而非 SPDY 採用的 DEFLATE。
雖然 HTTP/2 提升了網頁的性能,可是並不表明它已是完美的了,HTTP/3 就是爲了解決 HTTP/2 所存在的一些問題而被推出來的。隨着時間的演進,愈來愈多的流量都往手機端移動,手機的網絡環境會遇到的問題像是封包丟失機率較高、較長的 Round Trip Time (RTT)和鏈接遷移等問題,都讓主要是爲了有線網路設計的HTTP/TCP協議遇到貧頸。
咱們能夠看兩個典型的問題。
第一握手帶來的消耗。HTTP/2 使用 TCP 協議來傳輸的,而若是使用 HTTPS 的話,還須要使用 TLS 協議進行安全傳輸,而使用 TLS 也須要一個握手過程,這樣就須要有兩個握手延遲過程:
在創建 TCP 鏈接的時候,須要和服務器進行三次握手來確認鏈接成功,也就是說須要在消耗完 1.5 個 RTT 以後才能進行數據傳輸。
進行 TLS 鏈接,TLS 有兩個版本——TLS 1.2 和 TLS 1.3,每一個版本創建鏈接所花的時間不一樣,大體是須要1~2個 RTT。
總之,在傳輸數據以前,咱們須要花掉 3~4 個 RTT。
第二,TCP 的隊頭阻塞並無獲得完全解決。咱們知道,爲了實現多路複用,在 HTTP/2 中多個請求是跑在一個 TCP 管道中的。但當出現了丟包時,HTTP/2 的表現反倒不如 HTTP/1.X 了。由於 TCP 爲了保證可靠傳輸,有個特別的丟包重傳機制,丟失的包必需要等待從新傳輸確認,HTTP/2 出現丟包時,整個 TCP 都要開始等待重傳,那麼就會阻塞該 TCP 鏈接中的全部請求。而對於 HTTP/1.1 來講,能夠開啓多個 TCP 鏈接,出現這種狀況反到只會影響其中一個鏈接,剩餘的 TCP 鏈接還能夠正常傳輸數據。
至此,咱們很容易就會想到,爲何不直接去修改 TCP 協議?其實這已是一件不可能完成的任務了。由於 TCP 存在的時間實在太長,已經充斥在各類設備中,而且這個協議是由操做系統實現的,更新起來很是麻煩,不具有顯示操做性。
HTTP/3 乘着 QUIC 來了。
HTTP3 是基於 QUIC 的協議,如上圖。先說 QUIC,QUIC 協議是 Google 提出的一套開源協議,它基於 UDP 來實現,直接競爭對手是 TCP 協議。QUIC 協議的性能很是好,甚至在某些場景下能夠實現 0-RTT 的加密通訊。
在 Google 關於 QUIC [docs.google.com/document/d/…] 的文件中提到,與 HTTP/2 相比,QUIC 主要具備下列優點:
Reduce connection establishment latency (減小鏈接創建時間)
Improved congestion control (改進擁塞控制)
Multiplexing without head-of-line blocking (沒有隊頭阻塞的多路複用)
Forward error correction (修復以前的錯誤)
Connection migration(支持網絡遷移)
多路複用,避免隊頭阻塞
這句話提及來很容易,但理解起來並不那麼顯然,要想理解 QUIC 協議到底作了什麼以及這麼作的必要性,我想仍是從最基礎的 HTTP/1.0 聊起比較合適。
Pipiline
根據谷歌的調查, 如今請求一個網頁,平均涉及到 80 個資源,30 多個域名。考慮最原始的狀況,每請求一個資源都須要創建一次 TCP 請求,顯然不可接受。HTTP 協議規定了一個字段 Connection,不過默認的值是 close,也就是不開啓。
早在 1999 年提出的 HTTP 1.1 [www.ietf.org/rfc/rfc2616…] 協議 中就把 Connection 的默認值改爲了Keep-Alive,這樣同一個域名下的多個 HTTP 請求就能夠複用同一個 TCP 鏈接。這種作法被稱爲 HTTP Pipeline,優勢是顯著的減小了創建鏈接的次數,也就是大幅度減小了 RTT。
以上面的數據爲例,若是 80 個資源都要走一次 HTTP 1.0,那麼須要創建 80 個 TCP 鏈接,握手 80 次,也就是 80 個 RTT。若是採用了 HTTP 1.1 的 Pipeline,只須要創建 30 個 TCP 鏈接,也就是 30 個 RTT,提升了 62.5% 的效率。
Pipeline 解決了 TCP 鏈接浪費的問題,但它本身還存在一些不足之處,也就是全部管道模型都難以免的隊頭阻塞問題。
隊頭阻塞
咱們再舉個簡單並且直觀的例子,假設加載一個 HTML 一共要請求 10 個資源,那麼請求的總時間是每個資源請求時間的總和。最直觀的體驗就是,網速越快請求時間越短。然而若是某一個資源的請求被阻塞了(好比 SQL 語句執行很是慢)。但對於客戶端來講全部後續的請求都會所以而被阻塞。
隊頭阻塞(Head of line blocking,下文簡稱 HOC)說的是當有多個串行請求執行時,若是第一個請求不執行完,後續的請求也沒法執行。好比上圖中,若是第四個資源的傳輸花了好久,後面的資源都得等着,平白浪費了不少時間,帶寬資源沒有獲得充分利用。
所以,HTTP 協議容許客戶端發起多個並行請求,好比在筆者的機器上最多支持六個併發請求。併發請求主要是用於解決 HOC 問題,當有三個併發請求時,狀況會變成這樣:
可見雖然第四個資源的請求被阻塞了,可是其餘的資源請求並不必定會被阻塞,這樣總的來講網絡的平均利用率獲得了提高。
支持併發請求是解決 HOC 問題的一種方案,這句話沒有錯。可是咱們要理解到:「併發請求並不是是直接解決了 HOC 的問題,而是儘量減小 HOC 形成的影響「,以上圖爲例,HOC 的問題依然存在,只是不會太浪費帶寬而已。
有讀者可能會好奇,爲何很少搞幾個併發的 HTTP 請求呢?剛剛說過筆者的電腦最多支持 6 個併發請求,谷歌曾經作過實驗,把 6 改爲 10,而後嘗試訪問了三千多個網頁,發現平均訪問時間居然還增長了 5% 左右。這是由於一次請求涉及的域名有限,再多的併發 HTTP 請求並不能顯著提升帶寬利用率,反而會消耗性能。
SPDY 的作法
有沒有辦法解決隊頭阻塞呢?
答案是確定的。SPDY 協議的作法很值得借鑑,它採用了多路複用(Multiplexing)技術,容許多個 HTTP 請求共享同一個 TCP 鏈接。咱們假設每一個資源被分爲多個包傳遞,在 HTTP 1.1 中只有前面一個資源的全部數據包傳輸完畢後,後面資源的包才能開始傳遞(HOC 問題),而 SPDY 並不這麼要求,你們能夠一塊兒傳輸。
這麼作的代價是數據會略微有一些冗餘,每個資源的數據包都要帶上標記,用來指明本身屬於哪一個資源,這樣客戶端最後才能把他們正確的拼接起來。不一樣的標記能夠理解爲圖中不一樣的顏色,每個小方格能夠理解爲資源的某一個包。
TCP 窗口
是否是以爲 SPDY 的多路複用已經夠厲害了,解決了隊頭阻塞問題?很遺憾的是,並無,並且我能夠很確定的說,只要你還在用 TCP 連接,HOC 就是逃不掉的噩夢,不信咱們來看看 TCP 的實現細節。
咱們知道 TCP 協議會保證數據的可達性,若是發生了丟包或者錯包,數據就會被重傳。因而問題來了,若是一個包丟了,那麼後面的包就得停下來等這個包從新傳輸,也就是發生了隊頭阻塞。固然 TCP 協議的設計者們也不傻,他們發明了滑動窗口的概念:
這樣的好處是在第一個數據包(1-1000) 發出後,沒必要等到 ACK 返回就能夠馬上發送第二個數據包。能夠看出圖中的 TCP 窗口大小是 4,因此第四個包發送後就會開始等待,直到第一個包的 ACK 返回。這樣窗口能夠向後滑動一位,第五個包被髮送。
若是第1、2、三個的包都丟失了也沒有關係,當發送方收到第四個包時,它能夠確信必定是前三個 ACK 丟了而不是數據包丟了,不然不會收到 4001 的 ACK,因此發送方能夠大膽的把窗口向後滑動四位。
滑動窗口的概念大幅度提升了 TCP 傳輸數據時抗干擾的能力,通常丟失一兩個 ACK 根本不要緊。但若是是發送的包丟失,或者出錯,窗口就沒法向前滑動,出現了隊頭阻塞的現象。
QUIC 是如何作的
QUIC 協議基於 UDP 實現,咱們知道 UDP 協議只負責發送數據,並不保證數據可達性。這一方面爲 QUIC 的多路複用提供了基礎,另外一方面也要求 QUIC 協議本身保證數據可達性。
SPDY 爲各個數據包作好標記,指明他們屬於哪一個 HTTP 請求,至於這些包能不能到達客戶端,SPDY 並不關心,由於數據可達性由 TCP 協議保證。既然客戶端必定能收到包,那就只要排序、拼接就好了。QUIC 協議採用了多路複用的思想,但同時還得本身保證數據的可達性。
TCP 協議的丟包重傳並非一個好想法,由於一旦有了先後順序,隊頭阻塞問題將不可避免。而無序的數據發送給接受者之後,如何保證不丟包,不錯包呢?這看起來是個不可能完成的任務,不過若是把要求下降成:最多丟一個包,或者錯一個包。事情就簡單多了,操做系統中有一種存儲方式叫 RAID 5,採用的是異或運算加上數據冗餘的方式來保證前向糾錯(FEC: Forward Error Correcting)。QUIC 協議也是採用這樣的思想,這裏再也不贅述。
利用冗餘數據的思想,QUIC 協議基本上避免了重發數據的狀況。固然 QUIC 協議仍是支持重傳的,好比某些很是重要的數據或者丟失兩個包的狀況。
少 RTT,請求更快速
前面說到,一次 HTTPS 請求,它的基本流程是三次 TCP 握手外加四次 SSL/TLS 握手。也就是須要三個 RTT。可是 QUIC 在某些場景下,甚至可以作到 0RTT。
首先介紹下什麼是 0RTT。所謂的 0RTT 就是通訊雙方發起通訊鏈接時,第一個數據包即可以攜帶有效的業務數據。而咱們知道,這個使用傳統的TCP是徹底不可能的,除非你使能了 TCP 快速打開特性,而這個很難,由於幾乎沒人願意爲了這個收益去對操做系統的網絡協議棧大動手腳。未使能 TCP 快速打開特性的TCP傳輸第一筆數據前,至少要等1個RTT。
咱們這裏再說說 HTTP2。對於 HTTP2 來講,原本須要一個額外的 RTT 來進行協商,判斷客戶端與服務器是否是都支持 HTTP2,不過好在它能夠和 SSL 握手的請求合併。這也致使了一個現象,就是大多數主流瀏覽器僅支持 HTTPS2 而不單獨支持 HTTP2。由於 HTTP2 須要一個額外的 RTT,HTTPS2 須要兩個額外的 RTT,僅僅是增長一個 RTT 就能得到數據安全性,仍是很划算的。
TCP 快速打開
何謂 TCP 快速打開,即客戶端能夠在發送第一個 SYN 握手包時攜帶數據,可是 TCP 協議的實現者不容許將把這個數據包上傳給應用層。這主要是爲了防止 TCP 泛洪攻擊 [tools.ietf.org/html/rfc498…]。
由於若是 SYN 握手的包能被傳輸到應用層,那麼現有的防禦措施都沒法防護泛洪攻擊,並且服務端也會由於這些攻擊而耗盡內存和 CPU。
固然 TCP 快速打開並非徹底不可行的。人們設計了 TFO (TCP Fast Open),這是對 TCP 的拓展,不只能夠在發送 SYN 時攜帶數據,還能夠保證安全性。
TFO 設計了一個 Cookie,它在第一次握手時由 server 生成,Cookie 主要是用來標識客戶端的身份,以及保存上次會話的配置信息。所以在後續從新創建 TCP 鏈接時,客戶端會攜帶 SYN + Cookie + 請求數據,而後不等 ACK 返回就直接開始發送數據。
服務端收到 SYN 後會驗證 Cookie 是否有效,若是無效則會退回到三次握手的步驟,以下圖所示:
同時,爲了安全起見,服務端爲每一個端口記錄了一個值 PendingFastOpenRequests,用來表示有多少請求利用了 TFO,若是超過預設上限就再也不接受。
關於 TFO 的優化,能夠總結出三點內容:
TFO 設計的 Cookie 思想和 SSL 恢復握手時的 Session Ticket 很像,都是由服務端生成一段 Cookie 交給客戶端保存,從而避免後續的握手,有利於快速恢復。
第一次請求絕對不會觸發 TFO,由於服務器會在接收到 SYN 請求後把 Cookie 和 ACK 一塊兒返回。後續客戶端若是要從新鏈接,纔有可能使用這個 Cookie 進行 TFO
TFO 並不考慮在 TCP 層過濾重複請求,之前也有相似的提案想要作過濾,但由於沒法保證安全性而被拒絕。因此 TFO 僅僅是避免了泛洪攻擊(相似於 backlog),但客戶端接收到的,和 SYN 包一塊兒發來的數據,依然有可能重複。不過也只有多是 SYN 數據重複,因此 TFO 並不處理這種狀況,要求服務端程序自行解決。這也就是說,不只僅要操做系統的支持,更要求應用程序(好比 MySQL)也支持 TFO。
TFO 使得 TCP 協議有可能變成 0-RTT,核心思想和 Session Ticket 的概念相似: 將當前會話的上下文緩存在客戶端。若是之後須要恢復對話,只須要將緩存發給服務器校驗,而沒必要花費一個 RTT 去等待。
結合 TFO 和 Session Ticket 技術,一個原本須要花費 3 個 RTT 才能完成的請求能夠被優化到一個 RTT。若是使用 QUIC 協議,咱們甚至能夠更進一步,將 Session Ticket 也放到 TFO 中一塊兒發送,這樣就實現了 0-RTT 的對話恢復。
QUIC 是怎麼作的
讓咱們看看 QUIC 是怎麼作的。
首先聲明一點,若是一對使用 QUIC 進行加密通訊的雙方此前歷來沒有通訊過,那麼 0-RTT 是不可能的,即使是 QUIC 也是不可能的。
QUIC 握手的過程須要一次數據交互,0-RTT 時延便可完成握手過程當中的密鑰協商,比 TLS 相比效率提升了 5 倍,且具備更高的安全性。在握手過程當中使用 Diffie-Hellman 算法協商初始密鑰,初始密鑰依賴於服務器存儲的一組配置參數,該參數會週期性的更新。初始密鑰協商成功後,服務器會提供一個臨時隨機數,雙方根據這個數再生成會話密鑰。
具體握手過程以下:
(1) 客戶端判斷本地是否已有服務器的所有配置參數,若是有則直接跳轉到(5),不然繼續
(2) 客戶端向服務器發送 inchoate client hello(CHLO) 消息,請求服務器傳輸配置參數
(3) 服務器收到 CHLO,回覆 rejection(REJ) 消息,其中包含服務器的部分配置參數
(4) 客戶端收到 REJ,提取並存儲服務器配置參數,跳回到(1)
(5) 客戶端向服務器發送 full client hello 消息,開始正式握手,消息中包括客戶端選擇的公開數。此時客戶端根據獲取的服務器配置參數和本身選擇的公開數,能夠計算出初始密鑰。
(6) 服務器收到 full client hello,若是不一樣意鏈接就回復 REJ,同(3);若是贊成鏈接,根據客戶端的公開數計算出初始密鑰,回覆 server hello(SHLO)消息,SHLO 用初始密鑰加密,而且其中包含服務器選擇的一個臨時公開數。
(7) 客戶端收到服務器的回覆,若是是 REJ 則狀況同(4);若是是 SHLO,則嘗試用初始密鑰解密,提取出臨時公開數
(8) 客戶端和服務器根據臨時公開數和初始密鑰,各自基於 SHA-256 算法推導出會話密鑰
(9) 雙方更換爲使用會話密鑰通訊,初始密鑰此時已無用,QUIC 握手過程完畢。以後會話密鑰更新的流程與以上過程相似,只是數據包中的某些字段略有不一樣。
想起有一個名言:計算機領域沒有什麼問題是加一層解決不了的,若是有,就再加一層。網絡模型原本就是層層累加,到了 Web 得以快速生動的展示給人們以豐富的內容。從 HTTP 的演變過程當中,咱們能夠看到中間又累加了若干層。不知道之後,又會是怎麼樣呢?
你們會發現,筆者在文中不止一次提到了演變這個詞。是的,這是來自達爾文進化論中的理論。在筆者看來,「物競天擇,適者生存」的演變理論和計算機領域的技術變化是很相似的,只不過在這裏,不是天擇,而是人擇。由市場,由用戶來選擇。不知道接下來,做爲選擇者的咱們,又將怎樣主導技術的走向?