connection 特性

- 當發送一個 http2 請求的時候會建立一個 connection (TCP connection)
- 發送第二個請求的時候會延續使用第一個請求所建立的Connection
- 若是有多個 stream 的情況,每個 stream 是可以在同一個 tcp connection 併發傳輸,稱做為多路復用(multiplexing)
stream 特性
- 一條 TCP 連線上可以有多個處於 Open 狀態的 stream(併發傳輸)
- Client 可以主動建立 stream (ID基數 1,3,5,7,9…)
- Server 可以主動建立 stream (ID偶數 2,4,6,8,10…)
- 任何一端都可以自主關閉 stream
- 同一條 stream 裡面的 frame 都是有序地排列
Client 向 Server 請求時 Stream ID 會為基數(1,3,5,7,9…)

Server 向 Client 主動推送 Stream ID 會為偶數(2,4,6,8,10…)

frame 特性
- 可以包含一到多個 Header (Header很小只要一個message ,到Header很大會被切成多個frame)
- 可以包含零到多個 Data!
想像一下 http1.1 時的 body 跟 header 都變成了 frame
簡單的來說 body 也叫 frame , header 也叫 frame 只是裡面資料不同而已。
到這裡你能比較 http1.1 與 http2 的不同嗎 ?
- 在 http1.1 中 每一次請求都要重新建立一個 tcp connection
- 在 http1.1 中 每一次傳輸都要帶上重複的 header
- 在 http1.1 中 資料都是以純文本的方式傳輸
- 在 http1.1 中 server 無法主動推送資料給 client
- 在 http1.1 中 header 沒有進行壓縮
本篇文章還會再深入一點點解析 http2 stream 的相關知識
stream status
可以觀察下圖 stream status 的狀態變化,所有的 stream 一開始會處於 idle 狀態,最終會結束於 closed 狀態。
了解各個狀態監視如何切換的,需要知道 接收/發送 了什麼 flag,下方我列出了幾種會發送或是接收的 flag。
- HEADERS (H)
- PUSH_PROMISE (PP)
- END_STREAM (ES)
- RST_STREAM (RST)

這邊會簡單的說明一下幾種可能的狀況
-
idle 狀態轉換
- 當 stream 狀態處於 idle , server 收到 head (h) flag 時後會把當前 stream 進行狀態的切換,從圖中可以我們從看到 idle 收 h flag 後切換成 open 狀態。
- 當 stream 狀態處於 idle 狀態,如果這時候 Server 想要推送消息給 Client 端,那Server 會發出 PUSH_PROMISE (PP) Flag 這時 Server 會處於 reserved 狀態,這時候就要準備發送後面的資料(Head+Data)。
-
Open 狀態轉換
- 當 stream 狀態處於 open 狀態,這時我們如果發送(send)或是接收(recv) END_STREAM (ES) flag 的話,都會進入一個所謂的半關閉狀態(half closed)這時stream會等待接受flag進而轉變成close狀態。
-
reserved 狀態轉換
- 當 stream 狀態處於 reserved 狀態,這時候Server 一但推送了第一筆資料的Head出去就會把狀態切換成半關閉狀態 (half closed) 等待接受flag進而轉變成close狀態。
-
half closed 狀態轉換
- 當 stream 狀態處於 half closed 狀態,這時我們如果發送(send)或是接收(recv) END_STREAM (ES) 、RST_STREAM (RST) flag 的話,會把該 Stream 的狀態切換成Closed。
Data source:http2-in-action/
stream 的priorty順序
由於 http2 大量的運用在前後與端溝通上,就算工程師只關心後端服務之間的溝通也需要知道,關於 http2 stream 的 priorty 的相關知識可以幫助我們在設計服務的時候善用已經存在的欄位。
+---------------+
|Pad Length? (8)|
+-+-------------+-----------------------------------------------+
|E| Stream Dependency? (31) |
+-+-------------+-----------------------------------------------+
| Weight? (8) |
+-+-------------+-----------------------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
從上圖我們可以看到 E 、 Stream Dependency? 、 Weight? 三個欄位,這三個欄位就表示了 http2 stream 的 priorty 的狀態。
?表示是可以選的欄位
- Stream Dependency : 表示依賴其他 Stream 當其他 Stream 完成才能換該 Stream
- Weight : 表示該 Stream 的權重,預設值為 16 ,那可以設定的範圍在 1~256。
wireshark 動手做看看
直接從 wireshark 抓取 http2 的封包來觀察 priorty,看看 http2 priorty , Stream ID , Frame 到底是怎麼一回事。
下圖為 wireshark 抓取瀏覽器存取 https://www.nba.com/?37 的封包

Magic SETTINGS WINDOW_UPDATE
可以看到第一個封包是 client (192.168.51.150) 送往 server (23.11.187.239) 封包的簡易描述是 Magic 類型的封包,同時包含了SETTINGS frame 與 WINDOW_UPDATE。

Magic
- 點開magic stream 的話可以看到 client 發送了一個 PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
SETTINGS
- 點開SETTINGS stream 的話可以看到 client 發送了好幾個設定值,告訴了Server 我要設定成這樣。
- Header table size
- Max concurrent streams
- Max hader list size
- Unknown
WINDOW_UPDATE
- 點開WINDOW_UPDATE stream 的話可以看到 client 發送了一個設定,告訴Server最大我的資料只能傳輸多少。

Headers
可以看到 client 發送了好幾個 HEADS 給SERVER並且Stream ID 都是奇數,這表示Client 發送給Server 的請求Stream ID都會是奇數。接下來 Cleint 又分別要了 Head[3] , Head[5] , Head[7] 那這些 Header有什麼關聯?

stream ID priority
點開 Head[3] 可以看到這個請求的權重,那跟 Head[5] 有什麼差別

點開 Head[5] 可以看到 ,觀察後可以看到 Head[3] 的priority 大於 Head[5],權重計算後 Head[3] 要比 Head[5] 的資料更快被接收。
stream ID dependency
除了 priority 之外還有 stream ID dependency 可以從上圖觀察到這兩個請求都要依賴 stream ID 0,表示都要先接收完成stream ID 0才可以接收各自的資料。

Data
可以看到 Server 回傳了資料給 Client 端,如圖所示 Client 要了 Head[1]的資料, Server 就回傳了六筆資料給 Client 。
小結
重新複習了 http2 的特性,以前常提到得多路復用(multiplexing),Stream 以及 Frame 到底是什麼又有什麼特性,以及當 Client/Server 發送或是接收請求後 connection 會處於什麼狀態 做一個簡單的分析與介紹。
最後從 wireshark 觀察 http2 請求的內容,觀察 Stream ID 與 Stream ID priority ,共用同一個 Connection 有些請求需要先被執行才能在執行其他請求的依賴關係,除此之外 沒有依賴關係的請求需要按照 priority 的大小 優先處理。