一.HTTP的基礎概念
首先了解HTTP協議,他是目前主要使用在應用層的一種協議
http被稱為超文本傳輸協議
而https則是安全的超文本傳輸協議
本章節的內容首先就是對http做一個簡單的了解。
HTTP是一種應用層協議,是基于TCP/IP協議來傳遞信息的。
其中http1.0,http1.1,http2.0均為TCP實現,http3.0則是UDP實現。
說了半天,那啥是協議?
協議其實就是為了使數據在網絡上從源頭到達目的,網絡通信的參與方必須遵循相同的規則,這套規則稱為協議。
二.簡單的HTTP協議
2.1URI和URL
在學習HTTP協議之前,首先介紹一下URI和URL
URI (統一資源標識符),如下圖
URL(統一資源定位符)
其正是使用 Web 瀏覽器等 訪問 Web 頁面時需要輸入的網頁地址
那他們兩個有什么區別呢?
- URI(Uniform Resource Identifier):
是一個廣義的資源標識符,唯一標識某個資源(如網頁、圖片、文件等),不限定標識方式。
- URL(Uniform Resource Locator):
是URI的子集,不僅標識資源,還提供具體的訪問方式(如協議、路徑、服務器地址等)
也就是說url是uri的一種表示方式。
可能這樣說還是有點迷糊,那我就舉一個更為簡單的例子吧
URI = 資源唯一標識
(如身份證號,唯一但無法直接定位)
URL = 資源唯一標識 + 定位方法
(如家庭住址,既能標識也能找到置)
換句話說URI只能標記它是一個資源,但是無法判斷他在哪里,而URL不僅是資源還能定位
常見的uri還有郵箱(郵箱可以標識一個資源)
簡言之:URL是URI的一種具體實現,用于定位資源;URI更通用,僅需標識資源。
2.2 HTTP工作過程
再有了上面的了解之后,俺們就要正式的邁入http的介紹了。
既然http是一種協議,或者說一種規矩,那他是如何工作的呢?
讓我們接著往下看👀
當我們在瀏覽器輸入一個網址,此時瀏覽器就會給對應的服務器發送一個 HTTP 請求,對應的服務器收到這個請求之后,經過計算處理,就會返回一個 HTTP 響應。
并且當我們訪問一個網站時,可能涉及不止一次的 HTTP 請求和響應的交互過程。
這個過程的規定其實就是http協議,按照協議規定的格式進行信息傳遞。
基礎術語:
- 客戶端: 主動發起網絡請求的一端
- 服務器: 被動接收網絡請求的一端
- 請求: 客戶端給服務器發送的數據
- 響應: 服務器給客戶端返回的數據
2.3 http的特點
上述所說的請求和響應,其實也是http的特點,但他的特點遠不如此。
注?? :網絡編程中,并不只有一發一收的形式,還有一對多,多對多的形式。
先來說一下http的優點吧:簡單,靈活和易于拓展,應用廣泛和跨平臺
- 簡單就是指他的報文格式
header + body
,頭部信息也是key-value
簡單文本的形式 - 靈活和易于拓展就是指他協議里面的請求方法,uri/url,狀態碼等沒有被固定死,允許開發人員自定義和擴充
- 應用廣泛和跨平臺,就是指的臺式的瀏覽器到手機上的各種APP都是
當然不僅僅有優點,也有缺點(雖然說是缺點,其實就是http的一種特色)
http是一種無狀態,明文傳輸的協議,是不安全的
所謂的無狀態,其實就是沒有標識。
在第一次發送之后,如果再次由同一個客戶端發送請求,雖然兩者建立了鏈接,但仍舊會導致不知道誰發送的請求,因為沒有對第一次訪問的客戶端做身份記錄📝
舉個例子:
例如在登錄->添加購物車->下單->結算->支付,這系列操作都要知道用戶的身份才行,但是服務器并不知道這些操作是關聯的,就需要每一次都問一遍發信息的人的身份。
每一次的操作都要驗證消息,這樣的購物還舒服嗎?別問,問就是酸爽!
明文傳輸就是字面意思,不加密,直接把原文發過去,可以直接肉眼查看,為調試工作帶來了極大的方便
顯然這樣的做法肯定都是有問題的,那她不就是相當于信息裸奔嗎,暴露在了光天化日之下。
萬一里面有你的賬號和密碼,那.........
2.4 http的問題解決
在了解了http的無狀態之后,我們會發現如果沒狀態豈不是每一次都要做一次身份認證,那豈不是太麻煩了,每次訪問一個頁面就要重新登陸一次。
為了解決這個問題于是乎就引入了cookie的概念,也就是身份記錄。
Cookie技術通過在請求和響應報文中寫入Cookie的信息來控制客戶端的狀態
Cookie會根據從服務器端發送的響應報文中的一個叫做Set-Cookie的首部字段信息,通知客戶端保存Cookie,當下一次客戶端在往服務器發送請求的時候,客戶端會自動在請求報文中加入cookie值,在發送出去
服務端發現客戶端發送過來的Cookie后,會去檢查到底是哪一個客戶端發來的連接請求,然后對比服務器上的記錄最后得到之前的狀態。
為了解決明文傳輸問題和不安全,引入https。
https的內容會在后續介紹
3.HTTPS篇,也可以先去看一下https
三.HTTP的緩存技術
在之前的學習中我們會發現,如果每一次訪問,都需要去服務端查找所需要的資源,如果我發送同樣的求,那又需要建立連接,又要去服務端下面去查找內容,那這樣豈不是很浪費嘛?
那有沒有什么好的辦法可以解決重復性請求的問題呢?
當然是有的,http協議通過引入緩存技術,從而解決這個問題
HTTP的緩存實現主要有兩種方式,分別是強制緩存和協商緩存
3.1強制緩存
強制緩存指的是只要瀏覽器判斷緩存沒有過期,則直接使用瀏覽器的本地緩存,決定是否使用緩存的主動性在于瀏覽器這邊。
如下圖中,返回的是 200 狀態碼,但在 size 項中標識的是 from disk cache就是使用了強制緩存。
強制緩存是利用HTTP響應中的兩個字段實現的,他們都標識資源在客戶端緩存的有效期:
Cache-Control
, 是一個相對時間;Expires
,是一個絕對時間;
如果 HTTP 響應頭部同時有 Cache-Control 和 Expires 字段的話,Cache-Control 的優先級高于 Expires 。
Cache-control 選項更多一些,設置更加精細,所以建議使用Cache-Control 來實現強緩存。具體的實現流程如下:
- 當瀏覽器第一次請求訪問服務器資源時,服務器會在返回這個資源的同時在 Response 頭部加上 Cache-Control,Cache-Control 中設置了過期時間大小;
- 瀏覽器再次請求訪問服務器中的該資源時,會先通過請求資源的時間與 Cache-Control 中設置的過期時間大小,來計算出該資源是否過期,如果沒有,則使用該緩存,否則重新請求服務器;
- 服務器再次收到請求后,會再次更新 Response 頭部的 Cache-Control。
也就是在客戶端將這些資源存儲到了瀏覽器的緩存之中,當需要的再次訪問的時候,會先查詢本地緩存是否過期,如果過期則重新申請資源
3.2 協商緩存
但是強制緩存也會帶來一個問題就是,如果在這段未過期時間內,如果請求資源的內容發生了改變,那在看舊版的豈不是不太好?
別擔心,設計者也考慮到了這個問題(在這里之前可以先去看一下響應的狀態碼,4.2.1下面的內容)
于是乎就有了協商緩存這個東西。
當我們在瀏覽器使用開發者工具的時候,你可能會看到過某些請求的響應碼是304
,這個是告訴瀏覽器可以使用本地緩存的資源,通常這種通過服務端告知客戶端是否可以使用緩存的方式被稱為協商緩存。
上面就是一個協商緩存的過程,所謂的協商其實就是判斷要不要用本地緩存,那這個判斷條件就是我們之前所說的----這個資源有沒有被更新
協商緩存可以基于兩種頭部來實現,有兩種不同的方式
- 第一種:請求頭部中的
If-Modified-Since
字段與響應頭部中的Last-Modified
字段實現
- 第二種:請求頭部中的
If-None-Match
字段與響應頭部中的ETag
字段
那他們的區別又是什么呢?
其實看完這些字段,就能知道,第一種方式是基于時間實現的,第二種則是基于一個唯一標識
前者會出現時間回溯或者被篡改等問題,所以一般都是采用后者(除此之外還可以解決其他比如Etag可以解決1s內多次刷新的操作等等)
這里需要注意一點:協商緩存這兩個字段都需要配合強制緩存中 Cache-Control 字段來使用只有在未能命中強制緩存的時候,才能發起帶有協商緩存字段的請求。
下面是一個強制緩存和協商緩存的工作流程
簡單說一下采用Etag字段實現的協商緩存的流程:
- 當瀏覽器第一次請求訪問服務器資源時,服務器會在返回這個資源的同時,在 Response 頭部加上 ETag 唯一標識這個唯一標識的值是根據當前請求的資源生成的;
- 當瀏覽器再次請求訪問服務器中的該資源時,首先會先檢查強制緩存是否過期:
-
- 如果沒有過期,則直接使用本地緩存;
- 如果緩存過期了,會在 Request 頭部加上 If-None-Match 字段,該字段的值就是 ETag 唯一標識;
- 服務器再次收到請求后,會根據請求中的 If-None-Match 值與當前請求的資源生成的唯一標識進行比較:
-
- 如果值相等,則返回 304 Not Modified,不會返回資源;
- 如果不相等,則返回 200 狀態碼和返回資源,并在 Response 頭部加上新的ETag 唯一標識;
- 如果瀏覽器收到 304 的請求響應狀態碼,則會從本地緩存中加載資源,否則更新資源。
四.HTTP報文
在經過上面的學習之后,我想對http已經有了一些認識吧,接下來,就讓我們深入了解一下http報文,他到底是如何規范網絡傳輸的。
下面是報文的樣子。
- 請求報文
- 響應報文
這就是http協議的請求和響應的整體結構。下面就會圍繞這些內容展開。
4.1 http請求(Request)
4.1.1 認識URL
在之前曾說過URL和URI,在瀏覽器上,我們大多數是通過URL直接訪問數據。
在學習請求之前,要對URL有一個最為基礎的了解。
其實訪問一個網站,就是訪問這個網站對應服務器內的文件資源。
通過這個構成也不難發現。
但是實際上URL有些字段是可以不寫的,就比如登入信息,常見的形式就是協議名+服務器地址(也就是域名)
既然說到URL,有時候我們會發現為什么有的URL后面有一堆百分號以及一些亂七八糟的東西,這些東西又是干什么的?
接下來就來介紹一下
GET https://www.sogou.com/web?query=%E8%9B%8B%E7%B3%95&_asf=www.sogou.com&_ast=&w=01019900&p=40040100&ie=utf8&from=index-nologin&s_from=index&sut=1129&sst0=1646360982664&lkt=0%2C0%2C0&sugsuv=003B56A6DA4C2A82610BB3A8CFD5D583&sugtime=1646360982664 HTTP/1.1
這個URL就是一個搜索蛋糕的URL
我們會發現 query string 的有些值是 %E8%9B%8B%E7%B3%95
,0%2C0%2C0
通過 urlencode,知道 %E8%9B%8B%E7%B3%95
就是表示蛋糕
其實也就是所謂的url編碼
需要 urlencode 的原因:
- 這是因為像 /、?、: 等這樣的字符,已經被 url 當做特殊意義理解了,因此這些字符不能隨意出現。如果某個參數中需要帶有這些特殊字符,就必須先對特殊字符進行轉義,即 urlencode
- 一個中文字符由 UTF-8 或者 GBK 這樣的編碼方式構成,雖然在 URL 中沒有特殊含義,但是仍然需要進行轉義,否則瀏覽器可能把 UTF-8/GBK 編碼中的某個字節當做 URL 中的特殊符號
其次還有查詢參數,也就是在URL 中的 `?query=`、`?key=value` 這類結構是 URL 查詢參數(Query Parameters),它們的作用是向服務器傳遞額外的信息,通常用于動態網頁或接口請求中。
也就是在服務器中的后端代碼來提起url中這些查詢參數的內容。
如果有多個查詢參數,則通過&鏈接
4.1.2 認識方法(methed)
想必大家肯定都聽說什么get,post方法,那他們到底是干什么的,有什么區別?
這里知道的同學可以先跳過😊
主要介紹一下GET請求和POST請求
根據 RFC 規范,GET 的語義是從服務器獲取指定的資源
POST 的語義是根據請求負荷(報文body)對指定的資源做出處理
其實也沒啥好說的,總結下來就是GET請求時獲取定位資源,而POST請求則是對資源做出處理
其實主要要了解的一個問題就是-----GET和POST都是安全冪等的嘛?
首先說一下什么是安全和冪等:
- 在 HTTP 協議里,所謂的「安全」是指請求方法不會「破壞」服務器上的資源。
- 所謂的「冪等」,意思是多次執行相同的操作,結果都是「相同」的。
如果從 RFC 規范定義的語義來看:
- GET 方法就是安全且冪等的,因為它是「只讀」操作,無論操作多少次,服務器上的數據都是安全的,且每次的結果都是相同的。所以,可以對 GET 請求的數據做緩存,這個緩存可以做到瀏覽器本身上(徹底避免瀏覽器發請求),也可以做到代理上(如nginx),而且在瀏覽器中 GET 請求可以保存為書簽。
- POST 因為是「新增或提交數據」的操作,會修改服務器上的資源,所以是不安全的,且多次提交數據就會創建多個資源,所以不是冪等的。所以,瀏覽器一般不會緩存 POST 請求,也不能把 POST 請求保存為書簽。
做個簡要的小結:
GET 的語義是請求獲取指定的資源。GET 方法是安全、冪等、可被緩存的。
POST 的語義是根據請求負荷(報文主體)對指定的資源做出處理,具體的處理方式視資源類型而不同。POST 不安全,不冪等,(大部分實現)不可緩存。
注意, 上面是從 RFC 規范定義的語義來分析的。但是實際過程中,開發者不一定會按照 RFC 規范定義的語義來實現 GET 和 POST 方法。
比如:
- 可以用 GET 方法實現新增或刪除數據的請求,這樣實現的 GET 方法自然就不是安全和冪等
- 可以用 POST 方法實現查詢數據的請求,這樣實現的 POST 方法自然就是安全和冪等。
GET方法也是可以帶上body,并且GET的查詢參數也不是獨有的,,其他比如POST請求也可以有參數
4.1.3 認識請求報頭(header)
這個應該就能很好理解,其實所謂的請求報頭就是附帶的信息,用來描述這個請求。
接下來就簡單介紹一下這些鍵值對的作用,下面的是比較常見的
通用首部字段
先介紹一下通用的首部字段:通用就是指在請求和響應中雙方都會使用到的字段
- Cache-Control:之前在強制緩存那邊涉及過,就是操作緩存的工作機制
- Connection:Connection 字段可用于管理持久連接(也稱為長連接)。持久連接允許客戶端和服務器在請求/響應完成后不立即關閉 TCP 連接,以便在同一個連接上發送多個請求和接收多個響應。在 HTTP/1.1 協議中,默認使用持久連接。當客戶端和服務器都不明確指定關閉連接時,連接將保持打開狀態,以便后續的請求和響應可以復用同一個連接。在 HTTP/1.0 協議中,默認連接是非持久的。如果希望在 HTTP/1.0上實現持久連接,需要在請求頭中顯式設置 Connection: keep-alive。keep-alive:表示希望保持連接以復用 TCP 連接。close:表示請求/響應完成后,應該關閉 TCP 連接。
- Date:表示HTTP報文的日期和時間
- Pragma:是http1.1之前版本的歷史遺留字段,僅作為與http/1.0的向后兼容而定義
- Transfer-Encoding:規定了傳輸報文主體時采用的編碼方式。
- .......(還有幾個,個人感覺到時候下去了解一下就行)
請求首部字段
上面簡單介紹了一下常見的通用字段,接下來介紹一下request的請求報文的首部字段:
Accept 用戶代理可處理的媒體類型
Accept-Charset 優先的字符集
Accept-Encoding 優先的內容編碼
Accept-Language 優先的語言(自然語言)
Authorization Web認證信息
Expect 期待服務器的特定行為
From 用戶的電子郵箱地址
Host 請求資源所在服務器
If-Match 比較實體標記(ETag)
If-Modified-Since 比較資源的更新時間
If-None-Match 比較實體標記(與 If-Match 相反)
If-Range 資源未更新時發送實體 Byte 的范圍請求
If-Unmodified-Since 比較資源的更新時間(與If-Modified-Since相反)
Max-Forwards 最大傳輸逐跳數
Proxy-Authorization 代理服務器要求客戶端的認證信息
Range 實體的字節范圍請求
Referer 對請求中 URI 的原始獲取方
TE 傳輸編碼的優先級
User-Agent HTTP 客戶端程序的信息
實體首部字段
Allow 資源可支持的HTTP方法
Content-Encoding 實體主體適用的編碼方式
Content-Language 實體主體的自然語言
Content-Length 實體主體的大小(單位:字節)
Content-Location 替代對應資源的URI
Content-MD5 實體主體的報文摘要
Content-Range 實體主體的位置范圍
Content-Type 實體主體的媒體類型
Expires 實體主體過期的日期時間
Last-Modified 資源的最后修改日期時間
這里涉及了長連接的問題,對他做出一個解釋 : 在進行http傳輸之前首先要進行tcp的三次握手,建立之后才可以發送http報文,但是每一次發送完報文,它就會自動斷開tcp鏈接,導致每一次發送請求都需要建立連接,造成了很大的開銷,所以后續的版本里就引入了長連接,不會導致每次請求都需要建立和斷開連接。
這就是http請求報文的一個形式,他里面還有很多的參數,了解就行了,如果需要設置,通過對應語言的請求和響應結構體設置即可。
4.2 http響應(response)
4.2.1 狀態碼(status code)
狀態碼表示訪問一個頁面的結果(如訪問成功、失敗,還是其它一些情況等等),它是一個3位的整數,從 1xx、2xx、3xx、4xx、5xx,分為五個大類,每個大類的含義都不同。以下介紹一些常見的狀態碼及它的狀態碼解釋
- 1xx 狀態碼
屬于信息性狀態碼,表示服務器已收到請求,需要客戶端繼續操作或等待進一步處理。它們通常用于臨時響應,不會作為最終結果。
(這一塊了解就行)
- 2xx 狀態碼
基本上200就是表示成功,這是最常見的。
- 3xx 狀態碼
301表示永久重定向
302表示臨時重定向
(重定向相當于手機呼號的呼叫轉移功能,如果我們換了一個手機號,就可以去辦理該呼叫轉移業務,使朋友撥打你的舊號碼時,自動跳轉到新號碼)
304 表示可以調用本地緩存
- 4xx
404 notfound 即你發送的url請求在服務器上找不到
403 表示訪問被拒絕
405 表示你訪問的服務器不支持請求中的方法
- 5xx
500 表示服務器內部錯誤
504 表示當前服務器負載比較大,服務器處理單條請求的時耗很長,就會出現超時情況。
這里可能就會有同學有問題了,不是說http是無狀態的嗎,那為啥還要搞狀態碼呢?
這里明顯就是對這個無狀態的認識有點不太深入,這里的無狀態是指瀏覽器不會保存客戶端之前的請求。
而狀態碼則是描述單個請求的結果,換句話說也就是返回服務器當前的狀態,所以并沒有改變無狀態的本質。
4.2.2 認識響應報頭
響應報頭其實和請求報頭差不多,接下來簡單介紹一下:
他的結構也是三部分,只不過就是將請求首部字段換成響應首部字段,其他方面都是一樣的
響應首部字段
Accept-Ranges 是否接受字節范圍請求
Age 推算資源創建經過時間
ETag 資源的匹配信息
Location 令客戶端重定向至指定URI
Proxy-Authenticate 代理服務器對客戶端的認證信息
Retry-After 對再次發起請求的時機要求
Server HTTP 服務器的安裝信息
Vary 代理服務器緩存的管理信息
WWW-Authenticate 服務器對客戶端的認證信息
五.HTTP的不同版本
HTTP從1.0-到HTTP1.1, HTTP2.0 , HTTP3.0這個演變,都有什么方面的改變?
目前常用的http協議是1.1,但是2.0和3.0的趨勢也任然在上升。
5.1 HTTP1.0和1.1相比,提高了什么性能
HTTP1.0和HTTP1.1相比
- 使用長連接的方式改善了1.0版本短鏈接造成的開銷
- 支持管道(pipeline)網絡傳輸,只要第一個請求發送出去,就不必等其回來,就可以發送第二個請求,減少整體響應時間。
(什么是長連接,其實1.0版本中,每一次發送請求都需要建立連接,響應之后自動斷開,所謂的長連接,就是不會自斷斷開,不用每一次發請求都要建立連接)
雖然1.1版本改進了不少,但是仍舊存在性能瓶頸問題:
- 請求 / 響應頭部(Header)未經壓縮就發送,首部信息越多延遲越大,只能壓縮 Body 的部分。
- 發送冗長的首部。每次互相發送相同的首部造成的浪費較多
- 雖然解決了請求的擁擠,但是如果響應但服務端的響應也是依次的,會導致隊頭阻塞的問題(說白了就是第一個響應沒發出去,后面的響應都發不出去)
- 沒有請求優先控制
- 請求只能從客戶端開始,服務器只能被動響應。
5.2 HTTP/2 做了什么優化?
5.2.1 http2的優改進
首先要知道HTTP/2是基于HTTPS的,所以安全性是有保障的。
接下來看一張圖( ﹡?o?﹡ )
在1.1上的改進
- 頭部壓縮
- 二進制格式
- 并發傳輸
- 服務器主動推送資源
1.頭部壓縮
HTTP/2 會壓縮頭(Header)如果你同時發出多個請求,他們的頭是一樣的或是相似的,那么,協議會幫你消除重復的部分。
這就是所謂的 HPACK 算法:在客戶端和服務器同時維護一張頭信息表,所有字段都會存入這個表,生成一個索引號,以后就不發送同樣字段了,只發送索引號,這樣就提高速度了。
2.二進制格式
HTTP/2 不再像 HTTP/1.1 里的純文本形式的報文,而是全面采用了二進制格式,頭信息和數據體都是二進制,并且統稱為幀(frame):頭信息幀(Headers Frame)和數據幀(Data Frame)
這樣做就不需要再將明文轉化為二進制了,還增加了傳輸效率。
這個主要和編碼有關,就不過多介紹了,感興趣可以自行了解??·??·??*?? ??
3.并發傳輸
我們都知道HTTP/1.1是基于請求響應模型,也就是完成了這樣一個請求響應的事務之后,才會處理下一個事務。
這樣就會導致你在發送完一個請求,就會進入等待,但是后面還有請求,由于你隊頭在等待,就會導致后面的請求也進入等待,也就是造成了隊頭阻塞的問題。
而HTTP2就很牛逼了,引出來stream的概念,多個stream復用一個tcp連接
其實也就是并發的發送數據,這些數據都是一個個的幀(frame),
這里就會想他們都是幀,咋區分哪幾個幀是一個數據拆分的啊?
針對這個問題,采用了獨一無二的 Stream ID 來區分,接收端可以通過 Stream ID 有序組裝成 HTTP 消息,不同 Stream 的幀是可以亂序發送的,因此可以并發不同的 Stream ,也就是 HTTP/2 可以并行交錯地發送請求和響應。如下圖所示
4.服務器推送
也就是改變了傳統的請求響應模式,服務端也可以主動發請求。
客戶端和服務器雙方都可以建立 Stream,但Stream ID 也是有區別的,客戶端建立的 Stream 必須是奇數號而服務器建立的 Stream 必須是偶數號。
知道這些內容就行了,舉一個簡單的例子:
下面這張圖的請求部分可以看見stream4,這個其實就是服務端主動向客戶端推送的。
5.2.2 http2點缺陷
雖然http2看似解決了隊頭阻塞的問題,其實并沒有,只不過問題不出在http,而是在tcp上。在文章的開頭說過http2是基于tcp的。
那為什么這樣做會導致tcp隊頭阻塞呢?
TCP是基于字節流的協議,TCP 層必須保證收到的字節數據是完整且連續的,這樣內核才會將緩沖區里的數據返回給 HTTP 應用。那么當「前 1 個字節數據」沒有到達時,后收到的字節數據只能存放在內核緩沖區里,只有等到這 1 個字節數據到達時,HTTP/2 應用層才能從內核中拿到數據,這就是 HTTP/2 隊頭阻塞問題。
這樣解釋可能比較含糊,我說的簡單一點:再此之前,我們要知道一點那就是tcp是可靠的,準確的。正是因為如此,如果說中間傳輸的過程中,如果一個幀丟失了,也就是所謂的丟包了,就會導致tcp重傳,只有這個包被重新傳入,其它的http請求才會正常工作。
5.3 HTTP/3又有什么優化呢?
首先就是HTTP/1.1和HTTP/2都存在隊頭阻塞的問題。
1.1是通過管道解決了請求隊頭阻塞,但是沒有解決響應的隊頭阻塞
2通過多個請求復用一個tcp連接,解決了http的隊頭阻塞,但是一旦丟包就會導致tcp層隊頭阻塞。
HTTP/2隊頭阻塞的問題是由tcp導致的,所以HTTP/3就把他換成了UDP
UDP 發送是不管順序,也不管丟包的,所以不會出現像 HTTP/2 隊頭阻塞的問題。大家都知道 UDP 是不可靠傳輸的,但基于 UDP 的 QUIC 協議 可以實現類似 TCP 的可靠性傳輸。
QUIC主要有三個特點:
- 無隊頭阻塞
- 更快的連接建立
- 連接遷移
1.無隊頭阻塞
QUIC 協議也有類似 HTTP/2 Stream 與多路復用的概念,也是可以在同一條連接上并發傳輸多個 Stream,Stream 可以認為就是一條 HTTP 請求。
他有自己的一套機制可以保證傳輸的可靠性的。當某個流發生丟包時,只會阻塞這個流,其他流不會受到影響,因此不存在隊頭阻塞問題。這與 HTTP/2 不同,HTTP/2 只要某個流中的數據包丟失了,其他流也會因此受影響。
也就是QUIC 連接上的多個 Stream 之間并沒有依賴,都是獨立的,某個流發生丟包了,只會影響該流,其他流不受影響。
2.更快的連接建立
對于 HTTP/1 和 HTTP/2 協議,TCP 和 TLS 是分層的,分別屬于內核實現的傳輸層,openssl 庫實現的表示層,因此它們難以合并在一起,需要分批次來握手,先 TCP 握手,再 TLS 握手。
HTTP/3 在傳輸數據前雖然需要 QUIC 協議握手,但是這個握手過程只需要 1 RTT,握手的目的是為確認雙方的「連接 ID」,連接遷移就是基于連接 ID 實現的。
但是 HTTP/3 的 QUIC 協議并不是與 TLS 分層,而是 QUIC 內部包含了 TLS,它在自己的幀會攜帶 TLS 里的“記錄”,再加上 QUIC 使用的是 TLS/1.3,因此僅需 1 個 RTT 就可以「同時」完成建立連接與密鑰協商。
(RTT : 數據從發送端到接收端再返回發送端所需的時間)
甚至,在第二次連接的時候,應用數據包可以和 QUIC 握手信息(連接信息 + TLS 信息)一起發送,達到 0-RTT 的效果。
3.連接遷移
基于 TCP 傳輸協議的 HTTP 協議,由于是通過四元組(源 IP、源端口、目的 IP、目的端口)確定一條 TCP 連接。
那么當移動設備的網絡從4G切換到WIFI時,意味著IP地址變化了,那么就必須要斷開連接,然后重新建立連接。而建立連接的過程包含TCP三次握手和TLS 四次握手的時延,以及TCP慢啟動的減速過程,給用戶的感覺就是網絡突然卡頓了一下,因此連接的遷移成本是很高的。
而QUIC協議沒有用四元組的方式來“綁定”連接,而是通過連接ID來標記通信的兩個端點,客戶端和服務器可以各自選擇一組ID來標記自己,因此即使移動設備的網絡變化后,導致IP地址變化了,只要仍保有上下文信息(比如連接ID.TLS密鑰等),就可以“無縫”地復用原連接,消除重連的成本,沒有絲毫卡頓感,達到了連接遷移的功能。
所以,QuUC是一個在UDP之上的偽TCP+TLS+HTTP/2的多路復用的協議。
QUlC是新協議,對于很多網絡設備,根本不知道什么是QUIC,只會當做UDP,這樣會出現新的問題,因為有的網絡設備是會丟掉UDP包的,而QUIC是基于uDP實現的,那么如果網絡設備無法識別這個是QUIC包,那么就會當作UDP包,然后被丟棄。
HTTP/3現在普及的進度非常的緩慢,不知道未來UDP是否能夠逆襲TCP.