前後端開發必會的 HTTP 協議“十全大補丸”(萬字長文)

来源:https://www.cnblogs.com/yifeng-coding/archive/2022/12/25/17004821.html
-Advertisement-
Play Games

本文全面介紹了 HTTP 協議相關知識,包括 HTTP 請求報文、響應報文、持久連接、緩存、Cookie 以及 HTTP 版本升級等! ...


本文全面介紹了 HTTP 協議相關知識,包括 HTTP 請求報文、響應報文、持久連接、緩存、Cookie 以及 HTTP 版本升級等!

HTTP 協議全稱為 HyperText Transfer Protocol,即超文本傳輸協議。

  • 超文本:指文字、圖片、音頻、視頻、文件等的混合體,比如最常見的 HTML。
  • 傳輸:指數據從一方轉移到另一方,二者之間可能相距數千里。
  • 協議:指通信雙方所做的一些約定,比如怎麼開始通信、信息的格式與順序、怎麼結束通信等。

HTTP 協議是幹啥的呢? 答案是用於客戶端與伺服器端之間的通信。我們日常上網過程中最常見的就是 HTTP 協議了,瀏覽器是最常見的 HTTP 客戶端。

比如我們使用瀏覽器訪問淘寶時,瀏覽器就會發送一個遵循 HTTP 協議的請求報文到淘寶伺服器,告訴淘寶伺服器自己想要獲取淘寶首頁信息。

淘寶伺服器收到此報文後,則會發送一個同樣遵循 HTTP 協議的響應報文到瀏覽器,此響應報文中包含淘寶首頁的內容。

瀏覽器收到響應報文後解析內容並展示在界面上。

1. HTTP 請求

客戶端向伺服器端發送的信息稱為請求報文,一般結構如下:

(1)請求行

請求行用於說明要做些什麼,包含三部分內容,中間用空格分割。

  • 方法,指定要對請求資源做什麼樣的操作(比如查詢、修改等)。常見的方法有:GET、POST、PUT、DELETE、HEAD 等。

    在前後端分離開發中,經常會遵循 RESTful 設計風格,其使用 POST、DELETE、PUT、GET 分別表示對數據的增、刪、改、查。

  • 資源路徑,指定所要請求資源在伺服器中的位置。比如 /index.html,表示訪問伺服器根目錄下名字為 index 的 html 文件。

  • HTTP 版本,指定所使用的 HTTP 版本。目前使用最多的版本為 HTTP/1.1。

舉個慄子:

面試中常見的一個問題: GET 和 POST 的區別是什麼?,在這裡做一下解答。

  • 首先,一般 GET 請求參數存放在 URL 中,而 POST 請求參數存儲在請求體中

    因為參數放在 URL 中可以直接被看到,則 GET 請求相對 POST 請求更不安全。但並不是說 POST 請求安全,因為參數放在請求體中,如果不採取加密手段的話,技術人員抓包就能看到明文。

    同時各個瀏覽器對 URL 長度做了限制,比如 IE 瀏覽器限制 URL 的長度最大為 2KB,這就使得了 GET 請求傳輸的數據長度有了限制,而 POST 請求傳輸數據長度無限制。

  • 其次,一般 GET 請求用於獲取數據,POST 請求用於新增數據

    這裡需要提一下冪等性的概念。冪等性是指對於同一個系統,在同樣條件下,一次請求和重覆多次請求對資源的影響是一致的,不會因為多次請求而產生了副作用。

    GET 請求用於獲取資源,不會對系統資源進行改變,因此是冪等的。POST 用於新增資源,這意味著多次請求將創建多個資源,因此不是冪等的。

    基於這個特點,GET 請求可被緩存、可保留在瀏覽器歷史記錄中、瀏覽器回退不會產生副作用,而 POST 請求反之。

  • 最後,GET 和 POST 在本質上並無區別

    HTTP 的底層是 TCP,所以無論是 GET 還是 POST 底層都是通過 TCP 進行連接通信。

    我們可以給 GET 加請求體,給 POST 帶上 URL 參數,可以用 GET 請求新增數據,POST 請求查詢數據,實際上也是完全可行的。

(2)請求頭

請求頭用於向伺服器傳遞一些額外的重要信息,比如所能接收的語言等。

請求頭由欄位名和欄位值構成,二者之間用冒號進行分隔。常見的一些請求頭有:

請求頭 含義
Host 接收請求的功能變數名稱
User-Agent 客戶端軟體的名稱和版本號等相關信息
Connection 設置發送響應之後 TCP 連接是否繼續保持的通信選項
Cache-Control 控制緩存的相關信息
Referer 記錄請求的來源(當通過點擊超級鏈接進入下一個頁面時,會記錄上一個頁面的 URI)
Accept 客戶端可支持的數據類型, 以 MIME 類型來表示
Accept-Encoding 客戶端可支持的編碼格式
Accept-Language 客戶端可支持的語言
If-Modified-Since 用於判斷資源的緩存是否有效(客戶端通知伺服器,本地緩存的最後變更時間)
If-None-Match 用於判斷資源的緩存是否有效
Range 用於斷點續傳,指定第一個位元組的位置和最後一個位元組的位置。
Cookie 表示請求者的身份,用於保存狀態信息

(3)請求空行

請求空行用於表明請求頭已經結束。

(4)請求體

請求體用於傳送客戶端要發給伺服器的數據,比如請求參數,通常出現在 POST 請求方法中,而 GET 方法無請求體,它的請求參數直接會顯示在網址上面。

請求行和請求頭的數據都是文本形式且格式化的,而請求體不同,其可以包含任意的二進位數據,比如文本、圖片、視頻等等。

2. HTTP 響應

伺服器向客戶端發送的信息稱為響應報文,響應報文的結構一般如下:

(1)響應行

響應行用於說明對請求的處理情況,包含三部分內容,中間用空格分割。

  • HTTP 版本,指定所使用的 HTTP 版本。比如 HTTP/1.1 表示使用的 HTTP 版本是 1.1。

  • 狀態碼,以三位數字形式描述伺服器對請求的處理結果。比如 200 表示成功。

  • 消息短語,以文本形式描述伺服器對請求的處理結果。比如 OK 表示成功。

舉個慄子:

面試中常見的一個問題: HTTP 有哪些常見狀態碼?,在這裡做一下解答。

  • 200 OK :表示請求被正常處理,這是最常見的狀態碼。

  • 204 No Content:表示請求被正常處理,但在返回的響應報文中不含響應體內容。一般用在只是客戶端向伺服器發送信息,而伺服器不用向客戶端返回什麼信息的情況。不會刷新頁面。

  • 301 Moved Permanently :永久重定向,表示請求的資源已經被永久轉移了,新的 URL 定義在響應報文的 Location 欄位中,瀏覽器將自動獲取新的 URL 發出新的請求。

    對搜索引擎來說,如果是 301 永久性重定向,頁面會刪除失效的 URL 收錄、索引,並替換為新的 URL。兩個不同的 URL 有指向相同的內容,搜索引擎對這兩個 URL 收錄的情況不同,會導致 URL 的權重和排名被分散。做了重定向後,可以將權重和排名集中到重定向後的 URL,因此可以提升自然排名。

    場景:比如建設一個網站後,將網站的 url 變換了,重新申請一個功能變數名稱,但是希望之前 url 仍然可以訪問到,就可以做一個重定向到新的 url 下麵。比如京東最早網址 http://www.360buy.com 重定向到 http://www.jd.com

  • 302 Found :臨時重定向(即以後還可能有變化),表示請求的資源已被臨時分配了新的 URL,新的 URL 會在響應報文中的 Location 欄位中返回,瀏覽器將會自動使用新的 URL 發出新的請求。

    搜索引擎會保留原 URL 的收錄和索引,並新增新 URL 的收錄、索引,這樣更有利於頁面的程式化處理。

    比如用戶在未登錄時訪問個人中心頁面,這時可以臨時重定向到登錄的 url;

    或者協議發生變化,比如京東 http://www.jd.com 重定向到 https://www.jd.com

    再比如,今天夜裡網站後臺要系統維護,服務暫時不可用,這就屬於『臨時』的,可以配置成 302 跳轉,把流量臨時切換到一個靜態通知頁面,瀏覽器看到這個 302 就知道這隻是暫時的情況,不會做緩存優化,第二天還會訪問原來的地址。

  • 304 Not Modified:代表上次的文檔已經被緩存了,還可以繼續使用,即訪問緩存。

  • 400 Bad Request :一個通用差錯狀態碼,表示請求報文中存在語法錯誤,客戶端發生的錯誤。

  • 401 Unauthorized :用戶未認證。

  • 403 Forbidden :表示伺服器雖然收到了請求,但是拒絕提供服務,常見的原因是為沒有訪問許可權(即用戶未授權)。

  • 404 Not Found :表示請求資源不存在。

  • 500 Internal Server Error :表示伺服器出現錯誤,可能是出現了一些 Bug 或故障。

  • 502 Bad Gateway:通常是伺服器作為網關或代理時返回的錯誤碼,表示伺服器自身工作正常,訪問後端伺服器發生了錯誤(可能後端伺服器宕機了)。

  • 503 Service Unavailable:表示伺服器暫時處於超負載或者正在進行停機維護,暫時無法處理請求,可以稍後再試。Web 伺服器如果限流,就可以給超載的流量直接響應 503 狀態碼。

(2)響應頭

響應頭用於向客戶端傳遞一些額外的重要信息,比如響應內容的長度等。

響應頭由欄位名和欄位值構成,二者之間用冒號進行分隔。常見的一些響應頭有:

響應頭 含義
Date 日期時間信息,表示伺服器產生併發送響應報文的日期和時間。
Server 表示HTTP伺服器應用程式的信息,類似於請求報文中的 User-Agent
Location 此欄位會配合重定向使用,用於提供重定向後新的 URI。
Connection 設置發送響應之後 TCP 連接是否繼續保持的通信選項
Cache-Control 控制緩存的相關信息
Content-Type 伺服器返回的響應類型
Content-length 伺服器返回的響應長度
Content-Encoding 伺服器返回的響應編碼
Content-Language 伺服器返回的響應語言
Last-Modified 指定響應內容最後的修改時間
Expires 表示資源失效的時間,瀏覽器會在指定過期時間內使用本地緩存
Etag 用於協商緩存,返回一個摘要值
Accept-Ranges 用於斷點續傳,指定伺服器所支持的內容範圍
Set-Cookie 設置狀態信息

(3)響應空行

響應空行用於表明響應頭已經結束。

(4)響應體

響應體用於傳送伺服器要發給瀏覽器的正文。

同請求報文的請求體一樣,響應體可包含任意的二進位數據。瀏覽器收到響應報文後,則會將正文載入到記憶體,然後解析渲染,最後顯示頁面內容。

3. HTTP 持久連接

客戶端發送一系列請求給伺服器,如果伺服器與客戶端對每個請求/響應對都經過一個單獨的 TCP 連接發送,則稱為非持續連接,也稱為短連接;如果經過相同的 TCP 連接發送,則稱為持續連接,也稱為長連接。

比如打開一個 Web 頁面時,假設該頁面含有一個 HTML 基礎文件和 2 張圖片,如果客戶端與伺服器通過同一個 TCP 連接來獲取這 3 個數據,則為持續連接,如果通過建立 3 次不同的 TCP 連接,則為非持續連接。

非持續連接的缺點:

  • 每次建立連接需要三次握手過程,導致總的請求響應時間變長。當然也不是絕對的,如果多個連接可以並行請求,總響應時間可能變短,比如 Chrome 瀏覽器為了提升載入速度,可以同時打開 6 個並行連接,但多個並行連接會加重 Web 伺服器負擔。
  • 必須為每一個請求的對象建立和維護一個全新的連接,而每一個連接都需要客戶和伺服器分配 TCP 的緩衝區和保持 TCP 變數,使得 Web 伺服器存在嚴重的負擔,因為一臺 Web 伺服器可能同時服務於數以百計不同的客戶的請求。

HTTP(1.1 及之後) 預設採用持續連接方式,但也可配置成非持續連接方式。在報文中使用 Connection 欄位來表示是否使用持久連接。

  • 如果 Connection 欄位的值為 keep-alive,則表明此連接為持久連接,HTTP1.1 及以後可預設不寫。
  • 如果 Connection 欄位的值為 close,則表明要關閉連接。

註意:持久連接不是永久連接,一般在一個可配置的超時間隔後,如果此連接仍未被使用,HTTP 伺服器就會關閉該連接。

4. HTTP 緩存

對於一些短時間內不會產生變化的資源,客戶端(瀏覽器)可以在一次請求後將伺服器響應的資源緩存在本地,之後直接讀取本地的數據,而不必再重新發送請求。

我們經常會接觸到『緩存』這一概念,比如由於記憶體和 CPU 之間速度差距較大,為了進一步提升電腦性能,於是設計了 L1 緩存、L2 緩存等,讓 CPU 先從緩存中取數據,如果取不到,再去記憶體取。

又比如在後端開發中,由於資料庫一般存儲在硬碟上,讀取速度較慢,於是可能會採用 Redis 等記憶體資料庫作為緩存,先去 Redis 中取數據,如果取不到,再去資料庫中取。

再比如在操作系統中,由於頁表進行地址轉換的速度較慢,於是有了 TLB 快表,當需要進行邏輯地址到物理地址的轉換時,先去查詢速度更快的 TLB 快表,如果查不到,再去查詢頁表,此時 TLB 快表就是一種緩存。

緩存的主要目的在於提升查詢速度,一般邏輯如圖所示。

同樣,在 HTTP 設計中也有緩存的概念,主要是為了加快響應速度,HTTP 緩存的實現依賴於請求報文和響應報文中的一些欄位,分為強緩存協商緩存

(1)強緩存

強緩存指的是在緩存數據未失效的情況下,那麼就會直接使用瀏覽器的緩存數據,不會再向伺服器發送任何請求,邏輯類似於前面舉的 L1 緩存、Redis、TLB 快表。

具體實現主要是通過 Cache-Control 欄位和 Expires 欄位。

Cache-Control 是一個相對時間(即多長時間後過期,http1.1 規範),Expires 是一個絕對時間(即在某個時間點過期,http1.0 規範),如果兩個欄位同時存在,Cache-Control 的優先順序更高。

由於伺服器端時間和客戶端時間可能不同步,存在偏差,這也就是導致了使用 Expires 可能會存在時間誤差,因此一般更推薦使用 Cache-Control 來實現強緩存

以 Cache-Control 為例,強緩存的具體的實現流程如下:

  1. 當瀏覽器第一次請求訪問伺服器資源時,伺服器會在響應頭中加上 Cache-Control。Cache-Control 中可以設置以下內容。

    • max-age=秒,表示緩存將於指定毫秒值後過期。比如:cache-control: max-age=31536000,表示緩存將於 365 天後過期。

    • no-store,表示不允許緩存(包括強緩存和協商緩存)。

    • no-cache,表示不使用強緩存,而是使用協商緩存,即使用之前必須要先去伺服器端驗證是否失效,如果沒失效,則再使用緩存,如果失效了,則返回最新數據。等價於max-age=0, must-revalidate

    • must-revalidate,表示允許緩存,並且如果緩存不過期的話,先使用緩存,如果緩存過期的話,再去伺服器端進行驗證緩存是否還有效。

      這裡很多小伙伴可能會有疑問,即使沒有加上 must-revalidate,有了 max-age 後,緩存過期了不也會去伺服器驗證嗎,加不加 must-revalidate 有什麼區別呢?

      在 HTTP 協議規範中,允許客戶端在某些特殊情況下直接使用過期緩存,比如校驗請求錯誤時(如無法再次連通伺服器),而加上了 must-revalidate 後,在校驗請求錯誤時,會返回 504 錯誤碼,而不是使用過期緩存。

  2. 瀏覽器再次請求訪問伺服器中的該資源時,根據請求資源的時間與 Cache-Control 中設置的過期時間大小,計算出該資源是否過期,

    1. 如果沒有過期(且 Cache-Control 沒有設置 no-cache 屬性和 no-store 屬性),則使用該緩存,結束;
    2. 否則重新請求伺服器;

(2)協商緩存

協商緩存指的是當第一次請求後,伺服器響應頭 Cache-Control 欄位屬性設置為 no-cache 或者緩存時間過期了,那麼瀏覽器再次請求時就會與伺服器進行協商,判斷緩存資源是否有效,即資源是否進行了修改更新。

  • 如果資源沒有更新,那麼伺服器返回 304 狀態碼,表明緩存仍然可用,而不需要再次發送資源,減少了伺服器的數據傳輸壓力,並更新緩存時間。
  • 如果數據有更新,伺服器返回 200 狀態碼,新資源存放在請求體中。

協商緩存可以基於以下兩種方式來實現:

第一種(HTTP/1.0 規範):請求頭部中的 If-Modified-Since 欄位與響應頭部中的 Last-Modified 欄位:

  • Last-Modified:標示這個響應資源的最後修改時間。第一次請求資源後,伺服器將在響應頭中帶上此信息。
  • If-Modified-Since:當資源過期了,瀏覽器再次發起請求的時候帶上 Last-Modified 的時間(放在請求頭 If-Modified-Since 中),伺服器將此時間與被請求資源的最後修改時間進行對比,
    • 如果最後修改時間較大,說明資源有被修改過,則返回最新資源和 200 狀態碼;
    • 否則說明資源無新修改,返回 304 狀態碼。
  • 此種方式存在以下問題:
    • 基於時間實現,可能會由於時間誤差而出現不可靠問題,並且只能精確到秒級,在同一秒內,Last-Modified 無感知。
    • 如果某些文件被修改了,但是內容並沒有任何變化(比如只是修改時間發生了變化),而 Last-Modified 卻改變了,導致文件沒法使用緩存。

第二種(HTTP/1.1 規範):請求頭部中的 If-None-Match 欄位與響應頭部中的 ETag 欄位:

  • Etag:唯一標識響應資源,是一個 hash 值;第一次請求資源後,伺服器將在響應頭中帶上此信息。
  • If-None-Match:當資源過期了,瀏覽器再次向伺服器發起請求時,會將請求頭 If-None-Match 值設置為 Etag 中的值。伺服器將此值與資源的 hash 值進行比對,
    • 如果二者相等,則資源沒有變化,則返回 304 狀態碼。
    • 如果資源變化了,則返回新資源和 200 狀態碼。
  • 此種方式存在的問題在於計算 Etag 會消耗系統性能,但可以解決第一種方式所存在的問題,推薦使用。

註意 :

  • 如果 HTTP 響應頭部同時有 Etag 和 Last-Modified 欄位的時候,Etag 的優先順序更高,也就是先會判斷 Etag 是否變化了,如果 Etag 沒有變化,然後再看 Last-Modified。
  • Ctrl + F5 強制刷新,會直接向伺服器提取數據。
  • F5刷新或瀏覽器的刷新按鈕,預設加上 Cache-Control:max-age=0,即會走協商緩存。

HTTP 是一種無狀態協議,即其本身不會記憶請求和響應之間的通信狀態,那麼 Web 伺服器就無法判斷此請求到底來自於哪個用戶,HTTP 協議中並不會保存關於用戶的任何信息。這樣設計的好處是不需要額外資源保存用戶狀態信息,減少了伺服器的 CPU 及記憶體資源的消耗。

但是隨著 Web 的發展,很多業務需要保存用戶狀態。

  • 比如電商網站需要在用戶跳轉到其他商品頁面時,仍然可以保存用戶的登錄狀態。不然用戶每訪問一次網站都要重新登錄一下,過於繁瑣,體驗效果就很差。
  • 比如短視頻網站希望記錄用戶以前看過的視頻,以便之後向其精準化推薦感興趣的視頻。

為了實現保持狀態的功能,這就出現了 Cookie。Cookie (伺服器給的憑證)類似於我們逛商場時的會員卡(商家給的憑證),記錄著我們的身份信息,只要出示了會員卡,商場工作人員就能確定我們的身份。同樣的,只要給伺服器發送報文時帶上了 Cookie,他就知道我們是誰了。

Cookie 中可以包含任意信息,最常見的是包含一個伺服器為了進行跟蹤而產生的獨特的識別碼

舉個慄子:

張三在發出第一次請求後,伺服器將其狀態信息記錄下來,比如他的名字、年齡、地址、購物歷史等,並通過響應頭 Set-Cookie 欄位,給予其一個 id=12345 的獨特識別碼作為 Cookie,那麼其再次向伺服器發出請求時,瀏覽器會自動在請求報文中的 Cookie 欄位中帶上 id=12345,伺服器就可以通過這個查詢到張三的具體信息,從而實現了保持狀態的功能。

Cookie 屬性:

  • max-age:過期時間有多長(絕對時間,單位:秒)。
    • 負數,表示瀏覽器關閉即失效。預設即為 -1。
    • 正數:失效時刻= 創建時刻+ max-age。
    • 0:表示 Cookie 立即刪除,即 Cookie 直接過期(從而實現使 cookie 失效)。
  • expires:過期時間(相對時間)。
  • secure:表示這個 Cookie 只會在 https 的時候才會發送。
  • HttpOnly:設置後無法通過使用 JavaScript 腳本訪問,可以保障安全,防止攻擊者盜用用戶 Cookie。
  • domain:表示該 Cookie 對於哪個域是有效的。 (Cookie 預設是不能直接跨域訪問的,但是二級功能變數名稱是可以共用 cookie 的)

Cookie 的缺點是如果傳遞的狀態信息較多,使得包過大,將會降低網路傳輸效率。

一般瀏覽器限制 Cookie 大小為 4KB。

6. HTTP 版本

隨著互聯網的發展,HTTP 也在不斷升級打怪,下麵分別介紹一下 HTTP1.1、HTTP/2 以及 HTTP/3 在前一版本的改進之處。

(1)HTTP/1.1 相比 HTTP/1.0 性能上的改進

HTTP/1.1 是目前最常見的 HTTP 版本,其相對於 HTTP/1.0 有以下改進。

① 持久連接

這個在前文中已經提到過,HTTP/1.0 中一個 TCP 連接只能發送一個請求和響應,而 HTTP/1.1 進行了優化,同一個 TCP 連接可以發送多次 HTTP 請求,減少了建立和關閉連接的性能開銷。

Web 服務軟體一般都會提供 keepalive_timeout 參數,用來指定 HTTP 持久連接的超時時間。比如設置了 HTTP 持久連接的超時時間是 60 秒,Web 服務軟體就會啟動一個定時器,如果完成某個 HTTP 請求後,在 60 秒內都沒有再發起新的請求,就會觸發回調函數來釋放該連接。

② 管道機制

持久連接雖然可以多個請求復用同一個連接,但是每次都需要等到上一個請求響應完成後,才能發送下一個請求。

管道機制中,只要第一個請求發出去了,不必等其回來,就可以發第二個請求出去,即相當於同時發出多個請求,因而可以減少整體的響應時間。

雖然客戶端可以同時發出多個 HTTP 請求,不用⼀個個等待響應,但是伺服器必須按照接收請求的順序依次發送對這些管道化請求的響應,以保證客戶端能夠區分出每次請求的響應內容。這存在下麵問題:

如果服務端在處理一個請求時耗時比較長,那麼後續請求的處理都會被阻塞住,會導致客戶端遲遲收不到數據,這稱為「隊頭堵塞」。

實際上,雖然管道機制的想法很好,但實現卻非常困難,因而很多瀏覽器根本不支持它。一般為了提升性能,採用並行多個 TCP 連接的形式來實現請求的同時發送。

③ 緩存控制

前文已經提到過,HTTP/1.1 在 HTTP/1.0 基礎之上,增加了一些請求響應頭,以更好的實現對緩存的控制。比如

  • 新增 Cache-Control 代替原先的 Expires
  • 新增 If-None-MatchEtag 代替原先的 If-Modified-SinceLast-Modified

④ 斷點續傳

利⽤ HTTP 消息頭使⽤分塊傳輸編碼,將實體主體分塊傳輸。

(2)HTTP/2 相比 HTTP/1.1 性能上的改進

HTTP/2 協議本身是基於 HTTPS 的,因此更加安全,其相對於 HTTP/1.1 有以下改進。

① 頭部壓縮

HTTP/1.1 中的請求頭攜帶大量信息,而且每次都要重覆發送,即使是同樣的內容,每次請求都需要附帶,這會造成性能的損耗。HTTP/2 進行了優化,引入了頭信息壓縮機制

客戶端和伺服器同時維護一張頭信息表,高頻出現的欄位會存入這個表,生成一個索引號。發送報文時直接使用索引號替代欄位。另外,索引表中不存在的欄位使用哈夫曼編碼壓縮

同時,多個請求中,如果請求頭相同,則後續請求只需要發送差異的部分,重覆的部分無需再發送

② 二進位幀

HTTP/1.1 的報文為純文本格式,而 HTTP/2 的報文全面採用二進位格式,並將原始的報文拆分為頭信息幀(Headers Frame)和數據幀(Data Frame)。採用二進位格式有利於提升數據傳輸效率。

③ 多路復用

在 HTTP/2 中定義了流(Stream)的概念,它是二進位幀的雙向傳輸序列,一個數據流對應著一個完整的請求-響應過程,在同一個請求響應過程中,往返的幀會分配一個唯一的流編號(Stream ID)。

在流的支持下,HTTP/2 可以在一個 TCP 連接中傳輸多個請求或響應,而不用按照順序一一對應(即實現多路復用),因為它們屬於不同的流,所發送的幀頭部都會攜帶 Stream ID,可以通過此 Stream ID 有效區分不同的請求-響應。

因而 HTTP/2 解決了 HTTP/1.1 的『隊頭阻塞』問題,多個請求 - 響應之間沒有了順序關係,不需要排隊等待,降低了延遲,大幅度提高了連接的利用率。

舉個慄子:

在一個 TCP 連接裡面,伺服器同時收到了 A 請求和 B 請求,於是先回應 A 請求,結果發現處理過程非常耗時,於是就發送 A 請求已經處理好的部分,接著回應 B 請求,完成後,再發送 A 請求剩下的部分。

④ 服務端推送

在 HTTP/1.1 中,只能客戶端發起請求,伺服器對請求進行響應。

而在 HTTP/2 中,服務端可以主動給客戶端推送必要的資源,以減少請求延遲時間。

比如當客戶端向伺服器請求一個 HTML 文件後,伺服器除了將此 HTML 文件響應給客戶端外,還可以提前主動將此 HTML 中所依賴的 JSCSS 文件推送給客戶端,這樣客戶端在解析 HTML 時,無需耗費額外的請求去得到相應的 JSCSS 文件。

(3)HTTP/3 相比 HTTP/2 性能上的改進

Google 公司為瞭解決 HTTP/2 存在的一些問題,提出了 QUIC 協議,而 HTTP-over-QUIC 就是 HTTP/3,其相對於 HTTP/2 有以下改進。

① 無隊頭阻塞

前面提到,HTTP/2 通過多路復用解決了 HTTP1.1 的『隊頭阻塞』問題,但其只是解決了 HTTP 這一層面的『隊頭阻塞』問題,底層仍然採用的 TCP 連接,HTTP/2 並沒有解決 TCP 的『隊頭阻塞』問題。

TCP 是可靠的、面向位元組流的協議。HTTP/2 的多個請求雖然可以跑在同一個 TCP 連接中,但如果出現丟包現象,TCP 就需要進行重傳,這可能就會導致整個 TCP 連接上的所有流阻塞,直到丟的包重傳成功,這就是 TCP 的『隊頭阻塞』問題。

為瞭解決此問題,HTTP/3 底層不再使用 TCP,而是採用 UDP!而 UDP 是無連接的,多個流互相獨立,之間不再有依賴,因而即使某個流發生了丟包,只會對該流產生影響,並不會使得其他流阻塞!

這時候有的小伙伴可能會問了,HTTP/3 底層不採用 TCP,那怎麼保證可靠傳輸呢?答案就是 HTTP/3 在應用層自己重新實現了可靠性機制。也就是說,HTTP/3 將原先 TCP 協議提供的部分功能上移至 QUIC,而且進行了改進。

② 優化重傳機制

TCP 採用序號+確認號+超時重傳機制來保證消息的可靠性,即如果某條消息超過一定時間還沒有得到確認,則重新發送此消息。

由於網路擁堵情況不斷變化,因而消息的超時時間並不是固定的,而是通過不斷採樣消息的往返時間不斷調整的,但 TCP 超時採樣存在不准確的問題

舉個慄子:

客戶端發送一個序號為 N 的包,然後超時了(可能丟了,也可能網路堵塞了),於是重新發送一個序號為 N 的包,之後伺服器收到後返回一個確認號 ACK 為 N+1 的包。但此時客戶端並無法判斷這個確定包是對原始報文的確認還是重傳報文的確認,那麼此時往返時間應該如何計算呢?

  • 如果認為確認包是對原始報文的確認,則可能把時間算長了;
  • 如果認為確認包是對重傳報文的確認,則可能包時間算長了。

因而 TCP 的重傳超時時間計算不准確,如果計算偏大,則效率慢,很久才會重傳,而如果計算偏小,則可能確認報文已經在路上了,但卻重傳了!

QUIC 是如何解決此問題呢?其定義了一個遞增的序列號(不再叫 Seq,而是 Packet Number),每個序列號的包只發送一次,即使重傳相同的包,其序列號也不一樣

舉個慄子:

客戶端發送一個序號為 N 的包,然後超時了,於是重新發送一個相同的包,但序號不再是 N,而是 N+1;那麼如果返回的確認包 ACK 為 N+1,就是對原始報文的響應,如果 ACK 為 N+2,就是對重傳報文的響應,因而採樣時間計算相對更加準確!

那此時怎麼知道包 N 和包 N+1 是同一個包呢?QUIC 定義了一個 Offset 概念。發送的數據有個偏移量 Offset,可以通過 Offset 知道數據目前發送到了哪裡,因而如果某個 Offset 的包沒有收到確認,就重發。

③ 連接遷移

眾所周知,一條 TCP 連接是由四元組標識的,分別是源 IP、源埠、目的 IP、目的埠。一旦其中一個元素髮生了變化,就需要斷開重連。

當手機信號不穩定或者在 WIFI 與移動網路切換時,都將會導致重連,而重連就意味著需要重新進行三次握手,將產生一定的時延,用戶感到卡頓,體驗不友好。

而 QUIC 不採用四元組的方式標識連接,而是以一個 64 位的隨機數作為 ID 來標識,通過此連接 ID 標記通信的兩端,之後即使網路發生變化,IP 或埠變了,但只要 ID 不變,則無需重連,只需要復用原先連接即可,時延低,減少了用戶的卡頓感,實現連接遷移。

(4)總結


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在實際應用開發中,隨著項目業務逐漸複雜,耦合度會越來越高,維護成本也會直線上升,所以解耦也變得越來越重要。Prism框架為WPF開發中解耦提供了非常便捷的應用。今天主要以一個簡單的小例子,簡述WPF開發中Prism框架的簡單應用,如有不足之處,還請指正。 ...
  • AIR32F103CBT6 和 AIR32F103CCT6 分別帶 32K Byte和 64K Byte 記憶體. 對於48pin封裝的 AIR32F103, 32K和64K的記憶體已經是市面上M3晶元中相當不錯的容量, 至於64pin封裝的AIR32F103RPT6, 96K的記憶體只在市場上的高端型號... ...
  • 近年來,有關數據泄露相關的新聞事件屢見不鮮,不斷地引發大眾的討論和擔憂。各家企業都或多或少在承受相關的數據安全風險,這種可能性會給企業運行帶來額外的風險,包括大眾的質疑以及政府的處罰等。 Facebook超5億用戶個人數據遭到泄露; Elector Software投票應用泄露超650萬以色... ...
  • 案例介紹 歡迎來到我的小院,我是霍大俠,恭喜你今天又要進步一點點了!我們來用JavaScript編程實戰案例,製作提高打字速度的小游戲-調皮的字母。點擊與屏幕上字母相對應的按鍵,若按鍵與出現的字母一致,則可以獲得相應的分數。 案例演示 根據屏幕上隨機出現的字母來點擊鍵盤上對應的按鍵,可自行調節字母下 ...
  • 案例介紹 歡迎來的我的小院,我是霍大俠,恭喜你今天又要進步一點點了!我們來用JavaScript編程實戰案例,做一個乘法積分游戲。乘法游戲主要通過用戶輸入的數值和程式計算的數值進行對比,正確積一分,錯誤扣一分。通過實戰我們將學會JSON.parse方法、JSON.stringify方法、localS ...
  • 歡迎來的我的小院,恭喜你今天又要漲知識了! 案例內容 利用JavaScript實現搜索框的移動展開。 演示 學習 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>小院里的霍大俠</title> </hea ...
  • 轉義字元 一些特殊字元。 | 轉義字元 | 含義 | | : : | : : | | \n | 換行 | | \r | 回車 | | \0 | 結束字元 | | \s | 空格 | | \’ | 單引號 | | \" | 雙引號 | | \\ | 反斜杠 | 字元編碼 ASCII American ...
  • -- @param list_length 生成的數組長度 -- @param max_random_length 隨機數的最大範圍 math.generate = function (list_length, max_random_length) local random = {} local r ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...