Help us understand the problem. What is going on with this article?

[HTTP2] youtube.comでHTTP2のフロー制御のテスト

More than 3 years have passed since last update.

コメントを受けて一部修正しました。
他、データの加算が間違っていたので修正。

フロー制御

HTTP2にはストリームで受信できるデータ量をコントロールするフロー制御という仕組みがあります。

フロー制御では二つのことを決めています。

⚫️コネクション全体で受信できるデータ量 (INITIAL_WINDOW_SIZE)
  (デフォルト65535)
⚫️一つのストリームで受信できるデータ量
  (デフォルト65535)

これは最初に交換するSETTINGフレームでクライアントとサーバーが取り決めます。その後に一つのストリームでデータを受信するたびに減っていきます。
0になったらデータを受信することはできないので、WINDOW_UPDATEフレームで任意のバイト数を加算させます。

▪️step0.

初期値
INITIAL_WINDOW_SIZE 100
コネクション全体 100

▪️step1.
 ストリームID1で50K受信

ウィンドウサイズ
ストリームID1 50K
コネクション全体 50K

▪️step2.
 ストリームID1で50K受信

ウィンドウサイズ
ストリームID1 0K
コネクション全体 0K

▪️step3.
 WINDOW_UPDATEフレームでストリームID1に200K指定

ウィンドウサイズ
ストリームID1 200K
コネクション全体 0K

このままではコネクション全体が0なのでストリームID1に余裕があっても受信できない。

▪️step4.
 WINDOW_UPDATEフレームでストリームID0(コネクション全体)に200K指定

ウィンドウサイズ
ストリームID1 200K
コネクション全体 200K

これで再び受信可能になる。

▪️step5.
 WINDOW_UPDATEフレームでストリームID0(コネクション全体)を70000K指定

ウィンドウサイズ
ストリームID1 200K
コネクション全体 70200K

コネクション全体を初期値より増やすことが可能

つまり、受信時は両方減り、加算時は指定したものだけ加算します。

youtubeでテスト

youtbeでテストしてみたところ、データ受信量が多くて良いサンプルが採れました。
nghttp2を使用してフロー制御の簡単な例を見てみました。

vagrant@nghttp2:~$ nghttp -nv https://www.youtube.com/
[  5.016] Connected
[  5.022][NPN] server offers:
          * h2-15
          * h2-14
          * spdy/3.1
          * spdy/3
          * http/1.1
The negotiated protocol: h2-14

NPNによりHTTP2ドラフト14が採用されました。
続いてSETTINGフレームの交換が行われます。

[  5.030] recv SETTINGS frame <length=18, flags=0x00, stream_id=0>
          (niv=3)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
          [SETTINGS_MAX_FRAME_SIZE(0x05):16384]
[  5.032] recv WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=983041)
[  5.033] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
          (niv=2)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]

最初のやりとりの概要

youtubeサーバーは自身が受け取るストリームの初期ウィンドウサイズが65535。
自身が受け取る1回のフレームの最大サイズ(SETTINGS_MAX_FRAME_SIZE)が16384。
そのすぐ後でWINDOW_UPDATEでコネクション全体のウィンドウサイズに983041を加算してアップデートしている。

クライアントはストリームの初期ウィンドウサイズを65535に設定。

クライアント
 初期ストリームウィンドウサイズ 65535
 初期コネクションウィンドウサイズ 65535

[  5.034] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  5.034] send HEADERS frame <length=39, flags=0x05, stream_id=1>
          ; END_STREAM | END_HEADERS
          (padlen=0)
          ; Open new stream
          :method: GET
          :path: /
          :scheme: https
          :authority: www.youtube.com
          accept: */*
          accept-encoding: gzip, deflate
          user-agent: nghttp2/0.7.10-DEV
[  5.039] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  5.089] recv (stream_id=1) :status: 200
[  5.089] recv (stream_id=1) accept-ranges: none
[  5.090] recv (stream_id=1) alternate-protocol: 443:quic,p=0.5
[  5.090] recv (stream_id=1) cache-control: no-cache
[  5.090] recv (stream_id=1) content-type: text/html; charset=utf-8
[  5.090] recv (stream_id=1) date: Thu, 02 Apr 2015 12:00:48 GMT
[  5.090] recv (stream_id=1) expires: Tue, 27 Apr 1971 19:44:06 EST
[  5.090] recv (stream_id=1) p3p: CP="This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl=ja for more info."
[  5.090] recv (stream_id=1) server: gwiseguy/2.0
[  5.090] recv (stream_id=1) set-cookie: YSC=BR-dzjKtehY; path=/; domain=.youtube.com; HttpOnly
[  5.090] recv (stream_id=1) set-cookie: VISITOR_INFO1_LIVE=6ZUYN3dlY40; expires=Tue, 01-Dec-2015 23:53:48 GMT; path=/; domain=.youtube.com; HttpOnly
[  5.090] recv (stream_id=1) set-cookie: PREF=f1=50000000; expires=Tue, 01-Dec-2015 23:53:48 GMT; path=/; domain=.youtube.com
[  5.090] recv (stream_id=1) vary: Accept-Encoding
[  5.090] recv (stream_id=1) x-content-type-options: nosniff
[  5.090] recv (stream_id=1) x-frame-options: SAMEORIGIN
[  5.090] recv (stream_id=1) x-xss-protection: 1; mode=block; report=https://www.google.com/appserve/security-bugs/log/youtube
[  5.090] recv HEADERS frame <length=528, flags=0x04, stream_id=1>
          ; END_HEADERS
          (padlen=0)
          ; First response header

ここまでがACKの送受信、リクエストヘッダの送信とレスポンスヘッダの受信です。

[  5.091] recv DATA frame <length=5057, flags=0x00, stream_id=1>
[  5.496] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.504] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.505] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.505] recv DATA frame <length=11326, flags=0x00, stream_id=1>

「GET /」に対するbodyデータを受信しています。

5つのDATAフレームの受信で
5057 + 16384 + 16384 + 16384 + 11326 = 65535

65535消費したので、クライアントのコネクション全体とストリームID1の初期ストリームウィンドウサイズが0になりました。

[  5.505] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=32951)
[  5.506] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=32951)
[  5.512] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=33738)
[  5.513] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=33738)

クライアント側からWINDOW_UPDATEフレームを送信して、ストリームID1とストリームID0(コネクション全体)のウィンドウサイズを加算させています。

それぞれ
コネクション全体とストリームID1に対して「33738 + 32951 = 66689」なので、今のウィンドウサイズは66689になります。

この時に送信できるサイズは「2^31」(2147483648)なので、nghttp2がなぜ2回に小分けにして送信しているのかは今のところ謎です。

この時の状態

コネクション全体 66689
ストリームID1 66689

[  5.514] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.525] recv DATA frame <length=15298, flags=0x00, stream_id=1>
[  5.525] recv DATA frame <length=1269, flags=0x00, stream_id=1>
[  5.526] recv DATA frame <length=16384, flags=0x00, stream_id=1>

今回受信したデータ量
16384 + 15298 + 1269 + 16384 = 49335

コネクション全体 66689 - 49335 = 17354
ストリームID1 66689 - 49335 = 17354

[  5.526] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=32951)
[  5.527] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=32951)

今回、加算したデータ量
コネクション全体 32951
ストリームID1 32951

コネクション全体 17354 + 32951 = 50305
ストリームID1 17354 + 32951 = 50305

[  5.529] recv DATA frame <length=15115, flags=0x00, stream_id=1>
[  5.530] recv DATA frame <length=2239, flags=0x00, stream_id=1>

今回受信したデータ量
15115 + 2239 = 17354

コネクション全体 50305 - 17354 = 32951
ストリームID1 50305 - 17354 = 32951

以下同じ流れ。

[  5.532] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=33738)
[  5.533] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=33738)
[  5.534] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.536] recv DATA frame <length=16193, flags=0x00, stream_id=1>
[  5.537] recv DATA frame <length=374, flags=0x00, stream_id=1>
[  5.537] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=32951)
[  5.538] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=32951)
[  5.539] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.541] recv DATA frame <length=16010, flags=0x00, stream_id=1>
[  5.542] recv DATA frame <length=1344, flags=0x00, stream_id=1>
[  5.542] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=33738)
[  5.542] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=33738)
[  5.544] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.547] recv DATA frame <length=15040, flags=0x00, stream_id=1>
[  5.548] recv DATA frame <length=1527, flags=0x00, stream_id=1>
[  5.549] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=32951)
[  5.550] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=32951)
[  5.551] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.552] recv DATA frame <length=14857, flags=0x00, stream_id=1>
[  5.553] recv DATA frame <length=2497, flags=0x00, stream_id=1>
[  5.556] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=33738)
[  5.557] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=33738)
[  5.557] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.560] recv DATA frame <length=15935, flags=0x00, stream_id=1>
[  5.560] recv DATA frame <length=632, flags=0x00, stream_id=1>
[  5.563] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=32951)
[  5.564] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=32951)
[  5.565] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.569] recv DATA frame <length=15752, flags=0x00, stream_id=1>
[  5.570] recv DATA frame <length=1602, flags=0x00, stream_id=1>
[  5.570] recv DATA frame <length=965, flags=0x01, stream_id=1>
          ; END_STREAM
[  5.570] recv PING frame <length=8, flags=0x00, stream_id=0>
          (opaque_data=0000000000000000)
[  5.570] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=33549)
[  5.570] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])

最終的にウィンドウサイズは65000程度を保つようになっているようです。

受信ウィンドウの最大サイズは「2^31」(2147483648)で、WINDOW_UPDATEで加算させることができる最大サイズも「2^31」(2147483648)です。
しかしこのような巨大な数字は今のところは見たことがないです。

⚫️Twitter.comの場合

[  5.423] recv SETTINGS frame <length=6, flags=0x00, stream_id=0>
          (niv=1)
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65536]

⚫️nghttp2.orgの場合

[  5.135] recv SETTINGS frame <length=12, flags=0x00, stream_id=0>
          (niv=2)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]

⚫️http2.akamai.comの場合

[  5.040] recv SETTINGS frame <length=18, flags=0x00, stream_id=0>
          (niv=3)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
          [SETTINGS_MAX_HEADER_LIST_SIZE(0x06):16384]
[  5.041] recv WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=1)

ウィンドウサイズを減らされた場合

負の値を保持して待機する必要がある。

http://summerwind.jp/docs/draft-ietf-httpbis-http2-16/#section6-9-2

6.9.2. 初期フロー制御ウインドウサイズ

例えば、クライアントがコネクション確立と同時に60KBを送信し、サーバーが初期ウインドウサイズを16KBに設定した場合、クライアントは SETTINGS フレームの受信時に、使用可能なフロー制御ウインドウを-44KBに再計算します。クライアントは、WINDOW_UPDATE フレームが>ウインドウを正の値に戻すまで、負の値のフロー制御ウインドウを保持します。ウインドウが正の値に戻った後に、クライアントは送信を再開します。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした