HTTP2 協定長文詳解

2022-11-30 21:00:51

一、HTTP2 簡介

HTTP2 是一個超文字傳輸協定,它是 HTTP 協定的第二個版本。HTTP2 主要是基於 google 的 SPDY 協定,SPDY 的關鍵技術被 HTTP2 採納了,因此 SPDY 的成員全程參與了 HTTP2 協定製定過程。

HTTP2 是由網際網路工程任務組 (IETF) 的 httpbis(Hypertext Transfer Protocol Bis) 工作小組開發。

多數瀏覽器在 2015 年底支援了該協定。

二、HTTP1.1 遇到哪些問題

2.1 HTTP 1.1 協定繁瑣

HTTP 1.1 包含很多的細節設計,也預留了很多未來擴充套件選項。所以沒有軟體實現了協定中提及的全部細節。

比如 Head 頭協定內容也很多,有的相似性還比較高。

隨著網際網路發展,有一些不常用的功能後來慢慢被用上。比如 HTTP Pipelining,可以提高傳輸速度。

2.2 未充分利用 TCP 效能

HTTP1.1 很難充分利用 TCP 提供的效能。HTTP使用者端和瀏覽器就要找其它方法來解決頁面載入時間。

2.3 延遲問題

隨著網際網路的發展,傳輸資源的數量和 size 大小都增加了很多。比如很多網站首頁需要下載的資源數量明顯增加了很多,下載慢的話,渲染頁面時間就會增加,導致使用者體驗下降。HTTP 1.1 也提出了一種解決方案 - HTTP Pipelining 技術。

HTTP Pipelining 把多個 HTTP 請求放到一個 TCP 連線中來傳送,傳送過程中不需要伺服器對前一個情趣的響應。但是在使用者端,還是會按照傳送的順序來接收響應請求。這就導致一個問題,就是 Head-of-line blocking。

2.4 Head-of-line blocking(HTTP頭阻塞)

在 2.3 小結中簡單介紹了 HTTP Pipelining,這個技術也導致了一個問題 Head-of-line blocking。

HTTP 1.1 利用 pipelining 可以同時在一個 TCP 中傳送多個 HTTP 請求,但是使用者端接收伺服器端響應資訊時,還是按照傳送時的順序來接收響應資訊。

這會導致一個什麼問題呢?

使用者端接收時,如果第一個響應資訊慢,會導致後面的響應資訊阻塞。因為 http1.1 以前協定規定是一發一收這種模式,相當於一個先進先出的序列佇列。伺服器端為了按序返回響應資訊也會佔用很多伺服器資源。

比如一些資源傳送到伺服器:js、css,伺服器不能對他們進行分塊解析,需要解析完 js 後,才會去解析 css 資源,這中解析方式肯定是一種低效率的解析方式,需要改進。

上面就是 http1.1 協定遇到的一些主要問題。

三、HTTP2 怎麼解決上面問題

  1. 對 HTTP 頭欄位進行資料壓縮,使用 HPAKC 演演算法。

HTTP 頭欄位多,所以採用壓縮演演算法來進行壓縮大小傳輸。

  1. 修復 HTTP 頭阻塞(Head-of-line blocking)問題

這就與 HTTP2 協定設計有關,主要涉及 3 個概念,流、訊息、幀。

流:stream,存在於 TCP 連線中的一個虛擬連線通道。每個流通道都有一個整數 ID 標識,流還可以承載雙向訊息。

訊息:message,它是由資料框構成。

幀:data frame,HTTP2 中構成訊息的最小單位。訊息有一個或多個幀構成。資料框中有 1 個關鍵資料,這個幀屬於哪個資源。也就是解析後可以直接組成資源。

下面章節內容會詳細介紹 HTTP2 協定內容。

  1. 延遲問題

也是跟 HTTP2 協定設計相關。

HTTP2 的一些其他特性:

  • 多路複用
  • 伺服器端推播
  • 訊息報文是二進位制格式
  1. HTTP2 效能提升演示 demo

演示地址:https://http2.akamai.com/demo

四、HTTP2 特性介紹

  • 二進位制分幀
  • 頭部欄位壓縮
  • 多路複用
  • 伺服器端推播
  • 流量控制和資源優先順序

五、HTTP2 協定內容詳解

5.1 HTTP2 協定概述

HTTP2 是基於 HTTP 語意,它提供了一種優化傳輸機制。HTTP2 支援 HTTP1.1 所有核心特性,HTTP2 從以下幾個方面進行了改進:

  • HTTP2 中最小的傳輸單元叫做幀。HTTP2 定義了很多型別的幀,每個幀服務於不同的目的。例如 HEADERS 和 DATA 幀就構成了 HTTP 請求和應答的主體。還有其它的比如 WINDOW_UPDATE, PUSH_PROMISE 等幀型別用於支援 HTTP2 的其它特性。
  • 多路複用。每個 HTTP 請求/應答在各自的流(stream 也是 HTTP2 中的一個很重要概念)中完成資料交換。每個流都是相互獨立。因此如果一個請求/應答阻塞或者速度很慢,也不會影響其它流中的請求/應答處理。在一個 TCP 連線中就可以傳輸多個流資料而無需建立多個連線。
  • 流量控制和優先順序機制。這個可以有效利用流的多路複用機制。流量控制可以確保只有接收者使用的資料會被傳輸。優先順序機制可以確保重要的資源被優先傳輸。
  • HTTP 增加了一種新的互動模式。即伺服器端可以推播應答給使用者端。
  • HEAD 頭資料壓縮。因為 HTTP 頭包含了大量冗餘資料,HTTP2 對這些資料進行了壓縮,壓縮後對於請求大小的影響顯著,可以將多個請求壓縮到一個包中。
  • HTTP2 資料採用二進位制編碼,而不是原來的文字格式資料。

HTTP2 協定有兩個識別符號:

  • 字串 "h2" 標識使用了 TLS 的 HTTP2 協定。該識別符號用在 TLS-ALPN 的擴充套件欄位,以及其他需要標示執行於 TLS 之上 HTTP2 的地方。
  • 字串 "h2c" 標識在構建在 TCP 之上的 HTTP2 協定,它是明文傳輸。該識別符號用在 HTTP/1.1 的 Upgrade 首部欄位,以及其他需要標示執行於 TCP 之上 HTTP/2 的地方。"h2c" 字串保留在 ALPN extension 識別符號空間,但是實際上標示了一個不使用 TLS 的協定。

5.2 HTTP1.1 協定怎麼升級到 HTTP2 協定

5.2.1 HTTP 升級

因為 HTTP1.1 協定會存在很長時間,所以怎麼把 HTTP1.1 協定升級到 HTTP2 協定就很關鍵了。

使用者端發起一個 http URI 請求時,如果事前不知道下一跳是否支援 HTTP2,需要使用 HTTP Upgrade 機制。使用者端發起一個 HTTP1.1 請求,其中包含 "h2c" 的 Upgrade 首部欄位,該請求還必須包含一個 HTTP2-Settings 首部欄位。

例如:

GET / HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
  • Connection 連線方式是升級協定
  • Upgrade 升級到什麼協定,例子中是升級到 h2c

如果伺服器不同意升級或者不支援 Upgrade 升級,可以直接忽略,當成是 HTTP1.1 請求和響應就好了。

如果伺服器同意升級,響應格式為:

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c

[ HTTP/2 connection ...

HTTP 響應升級的狀態碼是 101(Switching Protocols)。在結束 101 響應的空行後,伺服器端可以開始傳送 HTTP2 資料框了。

更多詳細內容請檢視 RFC7540: https://www.rfc-editor.org/rfc/rfc7540

5.2.2 HTTPS 升級

以上都是沒有涉及到 HTTPS ,現在 HTTP2 幾乎都是用到了 HTTPS(TLS),這個又怎麼升級呢?

前面講到,執行在 TLS 之上的 HTTP2 協定標識為 「h2」,使用者端不能傳送 「h2c" 協定標識,伺服器端自然也不能選擇 "h2c" 協定響應。

多了 TLS 之後,使用者端和伺服器端雙方需等到成功建立 TLS 連線之後才能傳送應用資料。而建立 TLS 連線,雙方本來就要進行協商,引入 HTTP2 之後,要做的就是在原來協商機制中把 HTTP2 的協商機制加進去。

HTTP2 是基於 Google 的 SPDY 協定開發的,在 SPDY 中開發了一個名為 NPN (Next Protocol Negotiation,下一代協定協商)的 TLS 擴充套件協定。而在 HTTP2 中,NPN 被 HTTP2 修改為 ALPN(Application Layer Protocol Negotiation,應用層協定協商),它也是 TLS 的擴充套件協定。也就是說構築在 TLS 上的 HTTP2,使用 ALPN 擴充套件協定進行協商。

  • 網路分層, TLS 位於哪一層

  • TLS v1.2 握手協定簡略互動圖:
     Client                                               Server

      ClientHello                  -------->
                                                      ServerHello
                                                     Certificate*
                                               ServerKeyExchange*
                                              CertificateRequest*
                                   <--------      ServerHelloDone
      Certificate*
      ClientKeyExchange
      CertificateVerify*
      [ChangeCipherSpec]
      Finished                     -------->
                                               [ChangeCipherSpec]
                                   <--------             Finished
      Application Data             <------->     Application Data

(Message flow for a full handshake,https://www.rfc-editor.org/rfc/rfc5246#section-7.3,打 * 號都是可選項)

在 RFC5246 中 7.4.1.2 小結的 TLS 的 Client Hello 結構:

struct {
          ProtocolVersion client_version;
          Random random;
          SessionID session_id;
          CipherSuite cipher_suites<2..2^16-2>;
          CompressionMethod compression_methods<1..2^8-1>;
          select (extensions_present) {
              case false:
                  struct {};
              case true:
                  Extension extensions<0..2^16-1>;
          };
} ClientHello;

上面的 Extension 在 7.4.1.4 Hello Extensions 中有定義:

struct {
          ExtensionType extension_type;
          opaque extension_data<0..2^16-1>;
} Extension;

enum {
          signature_algorithms(13), (65535)
} ExtensionType;

Here:

-  "extension_type" identifies the particular extension type.

-  "extension_data" contains information specific to the particular
      extension type.
  • APEN 擴充套件

在 HTTP2 的 APNE 擴充套件結構在 rfc7301 中第 3 小結也有定義:

A new extension type ("application_layer_protocol_negotiation(16)")
is defined and MAY be included by the client in its "ClientHello"
message.

enum {
       application_layer_protocol_negotiation(16), (65535)
} ExtensionType;

The "extension_data" field of the
("application_layer_protocol_negotiation(16)") extension SHALL
contain a "ProtocolNameList" value.

opaque ProtocolName<1..2^8-1>;

struct {
       ProtocolName protocol_name_list<2..2^16-1>
} ProtocolNameList;

"ProtocolNameList" contains the list of protocols advertised by the
client, in descending order of preference.  Protocols are named by
IANA-registered,opaque, non-empty byte strings, as described further
in Section 6 ("IANA Considerations") of this document

更多資訊請檢視這裡:https://www.rfc-editor.org/rfc/rfc5246

5.3 HTTP2 協定內容解析

HTTP2 協定是由 HTTP1.1 升級而來,HTTP 的語意不變,提供的功能沒有變化,HTTP方法、狀態碼、URI和Header欄位這些都沒有變化。在 HTTP2 中,傳輸資料時編碼是不同的,與換行符分隔文字的 HTTP1.1 協定不同,HTTP2 中資料交換都被拆分為更小的訊息和幀,而每個訊息和幀都是用二進位制格式來編碼。幀是 HTTP2 中最小資料單元。

5.3.1 HTTP2 資料中幾個重要的概念-幀、訊息、流

  • 幀 frame:HTTP2 中最小通訊資料單元,每個幀至少包含了一個標識(stream identifier,簡稱stream id)該幀所屬的流。

  • 訊息 message:訊息由一個或多個幀組成。例如請求的訊息和響應的訊息。

  • 流 stream:存在於 HTTP2 連線中的一個「虛擬連線通道「,它是一個邏輯概念。流可以承載雙向位元組流,及是使用者端和伺服器端可以進行雙向通訊的位元組序列。每個流都有一個唯一的整數 ID(stream identifier) 標識,由發起流的一端分配給流。

    單個 HTTP2 連線可以包含多個同時開啟的流,任何一個端點(使用者端和伺服器端)都可以將多個流的訊息進行傳輸。這也是多路複用關鍵所在。一個 TCP 連線(HTTP2 連線建立在 TCP 連線之上)裡可以傳送若干個流(stream),每個流中可以傳輸若干條訊息(message),每條訊息由若干二進位制幀(frame)組成。

    任何一端都可以關閉流。在流上傳送訊息的順序很重要,最後接收端會把 Stream Identifier (同一個流) 相同的幀重新組裝成完整的訊息報文。特別是 HEADERS 幀和 DATA 幀的順序在語意上非常重要。

    HTTP2 中連線Connection、流Stream、訊息Message、幀Frame的關係示意圖如下:

​ (來自:https://hpbn.co/http2)

5.3.2 二進位制分幀層(HTTP2格式框架)

​ (來自:https://hpbn.co/http2)

從上圖可以看出,HTTP1.1 是明文文字,而 HTTP2.0 首部(HEADERS)和資料訊息主體(DATA)都是幀(frame)。frame 是 HTTP2 協定中最小資料傳輸單元。

5.3.3 幀Frame的格式

一旦建立了 HTTP2 連線,端點(endpoints)間就可以開始交換幀資料。

所有的幀資料都是以一個固定的 9 位元組開頭(Frame Payload之前),後面跟一個可變長度的有效負載Frame Payload,這個可變長度的長度值由欄位 Length 來表示。

幀的格式:

+-----------------------------------------------+
|                 Length (24)                   |
+---------------+---------------+---------------+
|   Type (8)    |   Flags (8)   |
+-+-------------+---------------+-------------------------------+
|R|                 Stream Identifier (31)                      |
+=+=============================================================+
|                   Frame Payload (0...)                      ...
+---------------------------------------------------------------+

(from:https://www.rfc-editor.org/rfc/rfc7540.html#section-4.1)

另外的一種表示法,這種表示方法感覺跟編碼形式比較接近:

HTTP Frame {
  Length (24),
  Type (8),

  Flags (8),

  Reserved (1),
  Stream Identifier (31),

  Frame Payload (..),
}

(來自:https://httpwg.org/specs/rfc9113.html)

  • Length:24 個 bit 的無符號整數,用來表示 Frame Payload 的長度佔用位元組數。這個 Length 值的範圍為 0 - 16384(2^14)。觸發接收方設定了一個更大的值 SETTINGS_MAX_FRAME_SIZE 。

    幀頭的 9 位元組不包含在這個 Length 值中。

  • Type:定義 Frame 的型別,8 bits 表示。幀型別決定了幀的格式和語意。實現的話必須忽略或拋棄未知型別的幀。

  • Flags:為幀Frame型別保留的 8 bit 的布林值,這個標誌用於特定的幀Frame型別語意。如果這個欄位沒有被標識為特定幀型別語意,那麼接收時必須被忽略,並且傳送時不設定(0x0)。

  • R(Reserved):一個保留的 1 bit 欄位,這個欄位語意未定義。傳送時必須保持未設定(0x0),接收時忽略。

  • Stream Identifier:流標識,31 bit 的無符號整數。值 0x0 保留給與整個連線相關聯的幀,而不是單個流。

  • Frame Payload:內容主體,由幀的型別決定。

5.3.4 有哪些幀型別Type

上一小節的 Type 欄位,即幀型別,在 HTTP2 中共分為 10 種型別:

每一種型別幀都由一個 8 位型別程式碼來識別。每種幀型別在建立和管理整個連線或單個資料流時都有不同的作用,

Type Code(Type 值) 說明
DATA 0x00 資料框(type=0x00),內容主題資訊Frame Payload。比如一個或多個 DATA 幀可用於傳輸請求或響應的資訊內容
HEADERS 0x01 頭幀(type=0x01),用於開啟一個流,另外還攜帶一個首部的塊片段
PRIORITY 0x02 優先順序幀(type=0x02),在 rfc9113 中這個欄位已棄用。PRIORITY 幀可以在任何流狀態下傳送,包括空閒或關閉的流
RST_STREAM 0x03 流終止幀(type=0x03),允許立即終止一個流。傳送 RST_STREAM 表示請求取消流或表明發生錯誤的情況
SETTINGS 0x04 設定幀(type=0x04),設定兩端連線方式的設定引數
PUSH_PROMISE 0x05 推播幀(type=0x05),伺服器端的推播,告訴對端打算推播資料給你了
PING 0x06 PING幀(type=0x06),確定一個空閒連線是否仍然可用,也可以測量端點間往返時間(RTT)。PING 幀可以從任意端點發出
GOAWAY 0x07 GOAWAY幀(type=0x07),用於發起關閉連線的請求,或發出嚴重錯誤的訊號。GOAWAY 允許端點優雅的停止接收新流,同時仍然完成對先前建立的流的處理。
WINDOW_UPDATE 0x08 WINDOW_UPDATE幀(type=0x08),用於實現流控。可以作用在單獨的某個流上(指定具體的 Stream Identifier ),也可以作用在整個連線上(Stream Identifier 為 0x0)。只有 DATA 幀會受到流控的影響
CONTINUATION 0x08 延續幀 (type=0x9),用於繼續傳送首部塊片段位元組序列

(from:https://httpwg.org/specs/rfc9113.html)

  • HTTP2 中幀 type 和 flags 可能的組合

說明:圖表中 x 符號表示該型別的幀的 flags 可以取的值

5.3.5 幾種常見幀的格式結構

DATA 幀
+---------------+
|Pad Length? (8)|
+---------------+-----------------------------------------------+
|                            Data (*)                         ...
+---------------------------------------------------------------+
|                           Padding (*)                       ...
+---------------------------------------------------------------+

(DATA Frame Payload,https://www.rfc-editor.org/rfc/rfc7540.html)

下面是來自 https://httpwg.org/specs/rfc9113.html 完整幀定義:

DATA Frame {
  Length (24),
  Type (8) = 0x00,

  Unused Flags (4),
  PADDED Flag (1),
  Unused Flags (2),
  END_STREAM Flag (1),

  Reserved (1),
  Stream Identifier (31),

  [Pad Length (8)],
  Data (..),
  Padding (..2040),
}

這個 DATA Frame 裡欄位,Length,Type,Unused Flags,Reserved 和 Stream Identifier 在 5.4.3 小節有介紹。下面介紹 DATA Frame 裡的額外欄位:

  • Pad Length (8):一個 8 bit 的欄位,表示填充物(Padding)的長度。這個欄位出現是有條件的,只有 Flags 設定為 PADDED 情況下才會出現。
  • Data:傳遞的應用程式資料。Data 資料長度等於 payload 長度減去其他出現欄位的長度。
  • Padding:填充的位元組,沒有具體語意,傳送時必須設為 0,作用是混淆報文長度。

DATA 幀的標識有:

  • END_STREAM(0x01): bit 的位 0 設定為 1,代表當前流的最後一幀
  • PADDED(0x08):bit 的位 3 設定為 1 代表存在 Padding
HEADERS 幀
+---------------+
|Pad Length? (8)|
+-+-------------+-----------------------------------------------+
|E|                 Stream Dependency? (31)                     |
+-+-------------+-----------------------------------------------+
|  Weight? (8)  |
+-+-------------+-----------------------------------------------+
|                   Header Block Fragment (*)                 ...
+---------------------------------------------------------------+
|                           Padding (*)                       ...
+---------------------------------------------------------------+

(HEADERS Frame Payload, https://www.rfc-editor.org/rfc/rfc7540.html)

來自 https://httpwg.org/specs/rfc9113.html HEADER 幀完整定義:

HEADERS Frame {
  Length (24),
  Type (8) = 0x01,

  Unused Flags (2),
  PRIORITY Flag (1),
  Unused Flag (1),
  PADDED Flag (1),
  END_HEADERS Flag (1),
  Unused Flag (1),
  END_STREAM Flag (1),

  Reserved (1),
  Stream Identifier (31),

  [Pad Length (8)],
  [Exclusive (1)],
  [Stream Dependency (31)],
  [Weight (8)],
  Field Block Fragment (..),
  Padding (..2040),
}

主要看 HEADERS Frame Payload 這部分欄位,其它部分欄位在 5.4.3 小節有介紹。

  • Pad Length (8):8 bit長度,指定下面欄位 Padding 長度。該欄位只有在 PADDED 標識被設定後才會出現。
  • Exclusive (1):一個 bit 標。這個欄位只有在 PRIORITY 標識被設定時才會出現。在 RFC9113 中,在 HEADERS 幀中的優先順序訊號已被棄用,見 Priority Signaling
  • Stream Dependency (31):指定一個長度為 31 bit 的 stream identifier。該欄位只有在設定了 PRIORITY 標識時才會出現。
  • Weight (8):一個無符號的 8 bit 整數。該欄位只有在設定了 PRIORITY 標識時才會出現。
  • Field Block Fragment:header 塊片段
  • Padding:填充的八位位元組,沒有語意,傳送時填充值設定為 0。接收方沒有義務驗證該值。

HEADERS 幀的標識有:

  • END_STREAM(0x01):bit 的位 0 設定為 1,表示當前 header 塊傳送的最後一塊,但是帶有 END_STREAM 標識的 HEADERS 幀後面還可以跟 CONTINUATION 幀 (這裡可以把 CONTINUATION 看作 HEADERS 的一部分)
  • END_HEADERS (0x04):bit 的位 2 設定為 1,表示此幀包含整個欄位塊,並且後面沒有 CONTINUATION 幀。沒有設定 END_HEADERS 標識的 HEADERS 幀必須跟在同一流的 CONTINUATION 幀之後。
  • PADDED (0x08):bit 的位 3 設定為 1,PADDED 設定後表示 Pad Length 欄位以及它描述的 Padding 是存在的。
  • PRIORITY (0x20):bit 的位 5 設定為 1,表示存在 Exclusive Flag (E), Stream Dependency 和 Weight 3 個欄位。
更多幀型別格式

除了上面介紹的 2 種幀型別格式結構,還有 8 種其他的幀型別格式,更多內容請檢視下面的 RFC:

六、HTTP2 重要特性詳解

在第四節概要性的介紹了 HTTP2 的一些特性,這一節詳細介紹下。

6.1 多路複用

在 HTTP1.1 中,一個 HTTP 的資料傳輸需要建立一個 TCP 連線,雖然有 Pipleing 特性,但是又有對頭阻塞的問題。

在 HTTP2 中,在一個 TCP 連線中,可以發起多個 HTTP2 連線請求,而每個 HTTP2 連線中又可以發起多個流來傳輸資料。這都得益於 HTTP2 中資料格式的設計,最重要的流概念,在上面 5.3.1 小節中有介紹這個流的概念。

來看一看網路上的一張圖,HTTP1.1 和 HTTP2 的連線傳輸對比圖,原始出處我沒有找到:

從圖中可以看到在 HTTP1.1 中,請求 index.html 資源,響應完畢後就關閉連線了。

而在 HTTP2 中,請求完資源後,連線仍然是開啟的,後面還可以繼續使用這個連線通道傳輸資料。

上面是從資源傳輸的角度對比了 2 者的特性。

下面從 HTTP2 中 stream(流)角度來看看多路複用:

​ (來自:https://hpbn.co/http2/)

上圖可以看到在一個 HTTP2 connection 中,使用者端和伺服器端雙方都能夠向對方傳送多個流資料(stream 1、stream 3、stram 5),在 HTTP2 中用這個 stream ID 來標識幀和流的對應關係。

這也就是為什麼說流是多路複用中一個很重要的概念原因。

更多關於 stream流 和 multiplexing多路複用的內容,請檢視連結:https://httpwg.org/specs/rfc9113.html

6.2 頭部壓縮

HTTP 1.1 請求頭的協定內容很多,而且大部分都是重複的。在 HTTP1.1 中每次請求都會大量攜帶這種冗餘的頭資訊,浪費流量。

在 HTTP2 中,設計了 HPACK 壓縮演演算法對頭部協定內容進行壓縮傳輸,這樣不僅資料傳輸速度加快,也能節省網路流量。

HPACK 原理:

  • 使用者端和伺服器端共同維護了一份靜態字典表(Static Table),其中包含了常見頭部名及常見頭部名稱與值的組合的程式碼。
  • 使用者端和伺服器端根據先入先出的原則,共同維護了一份能動態新增內容的動態字典表(Dynamic Table)。
  • 使用者端和伺服器端支援基於靜態哈夫曼碼錶的哈夫曼編碼(Huffman Coding)

​ (來自 HTTP/2 is here, let's optimize

更多內容檢視 RFC:https://www.rfc-editor.org/rfc/rfc7541.txt

6.3 伺服器端推播

伺服器端推播是一種在使用者端請求之前傳送資料的機制。在 HTTP2 中,伺服器可以對使用者端的一個請求傳送多個響應。除了對原始請求響應外,還可以向用戶端推播額外的資料。

伺服器端推播的目的是讓伺服器通過預測它收到請求後有哪些相關資源需要返回,從而減少資源請求往返次數。

比如在 HTML 頁面的請求後,通常是對該頁面應用的樣式表和指令碼的請求,當這些資源被伺服器端直接推播給使用者端時,使用者端就不需要單獨給伺服器傳送請求來獲取這些資源了。

https://hpbn.co/http2/ 中有一幅圖可以用來說明伺服器端推播機制:

​ (來自 https://hpbn.co/http2/)

在 page.html 檔案中包含資原始檔 script.js 和 style.css。使用者端向伺服器端請求 page.html 檔案,伺服器端發現 page.html 檔案中包含了這兩種資原始檔,就會把這兩種資原始檔推播給使用者端,以此來減少使用者端的請求次數。如上圖。

所有伺服器端推播資料流都由 PUSH_PROMISE 幀發起。

在實踐中,伺服器端推播很難有效使用。因為需要伺服器端正確預測使用者端發出的額外請求,預測必須同時考慮快取、內容協商和使用者行為等因素。預測錯誤又可能導致效能下降,因為伺服器端傳送了額外的資料。特別是推播了大量資料時可能與重要的響應資料發生線路爭用等問題。

使用者端可以請求禁用伺服器端推播,SETTINGS_ENABLE_PUSH 設定為 0 就可以了。

6.4 流量控制

在 HTTP2 中,使用流來實現多路複用,這種會對 TCP 連線使用產生競爭,從而導致流傳輸被阻塞。流量控制是確保在同一連線上的流不會相互干擾。流量控制既能用於單個流也能用於整個連線。

HTTP2 使用 WINDOW_UPDATE 幀提供流量控制功能。

6.4.1 流控的一些原則

HTTP2 流控協定旨在不修改協定基礎上使用各種流控演演算法,流量控控的一些特點:

  • 1、流量控制作用於連線。所有型別流控都是作用於兩個單跳的端點之間,而不是整個端到端的鏈路。

  • 2、流量控制基於 WINDOW_UPDATE 幀實現。接收端公佈自己打算在每個流及整個連線上接收多少個八位位元組。這是一個基於信用的方案。

  • 3、流量控制具有方向性,總體控制是由接收端提供的。接收端可以為每個流和整個連線設定它所需要的任何視窗大小。傳送端必需遵循接收端設定的流量控制限制。使用者端、伺服器端和中間設施,作為接收端均獨立宣告其流控視窗,並在傳送時同樣遵循其對等端設定的流控限制。

  • 4、對於所有的流和連線,流量控制視窗的初始值大小為 65535 位元組。

  • 5、幀型別也決定了是否要在這個幀上應用流控。對於在 rfc9113 檔案中提到的幀型別,只有 DATA 幀是流控的作用物件。其他所有幀均不佔用流控視窗空間,這確保了重要的控制幀不會被流控阻塞。

  • 6、端點可以選擇禁用自己的流量控制,但端點不能忽略來自其對等端點的流量控制訊號。

  • 7、HTTP2 中僅定義了 WINDOW_UPDATE 幀的格式和語意。在這篇檔案 rfc9113 中沒有規定接收端如何決定何時傳送此幀或傳送的值,也沒有規定傳送端如何選擇傳送封包。協定的實現者可以選擇任意一種符合其需求的演演算法。

更多內容請檢視這篇檔案:https://httpwg.org/specs/rfc9113.html.2

6.5 流優先順序

在像 HTTP2 這樣的使用了多路複用的協定中,為流分配頻寬和計算資源的優先次序對於實現良好的效能至關重要。糟糕的優先順序方案可能導致 HTTP2 協定糟糕的效能。

在 HTTP2 中,一個訊息可以拆分為多個單獨的幀,並且允許來自多個流的幀被多路複用,使用者端和伺服器端幀傳輸可能是亂序傳輸,所以優先順序順序就變成了一個關鍵效能考慮因素。還有一種重要情況是,當傳送受到限制情況下,可以通過優先順序順序選擇那個流傳輸幀。顯示設定過優先順序的流將被優先安排。但這種優先並不能保證一個優先順序高的流能得到優先順序處理或優先順序傳輸。所以說,優先順序僅僅作為一種建議存在。

HTTP2 允許每個流具有關聯的權重和依賴性:

  • 每個流可以分配一個 1-256 範圍之間整數權重
  • 每個流都可以被賦予對另外一個流的依賴性

更多流優先順序資訊,比如流的依賴關係、依賴權重、優先順序(依賴)重排、優先順序狀態管理等內容,請檢視:https://httpwg.org/specs/rfc9113.html.3

感覺有幫助的可以點個推薦

七、參考