Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save numb86/84ffcc2379f4fd24ae54b297f7d78405 to your computer and use it in GitHub Desktop.
Save numb86/84ffcc2379f4fd24ae54b297f7d78405 to your computer and use it in GitHub Desktop.
『ハイパフォーマンス ブラウザネットワーキング』のメモ。

『ハイパフォーマンス ブラウザネットワーキング』

本書では、ブラウザに関連するネットワーク技術について扱っている。
その範囲は広く、TCP/IP や TLS のような土台となるような知識も、HTTP/2.0 や WebRTC のような最新の知識も、両方扱っている。
そしてそれらを、パフォーマンスという文脈で説明していく。パフォーマンス向上のために、どのようなことをするのか、そしてそれはなぜなのかを、解説していく。そしてそれを理解するためには、様々なネットワークプロトコルについて、その特徴や制限を知らなければならない。だから、パフォーマンスに関する本質的な知識を学んでいくことが、ネットワークについて学ぶことにもなる。

1章 レイテンシ・帯域幅入門

ウェブサイトのスピードの重要性は、計測によって証明されている。速ければ速いほど、エンゲージメントやコンバージョンが高まる。だから、スピードは機能なのだと言える。
100ミリ秒以上の遅延がシステムで発生すると、人間はそれを知覚する。

レイテンシと帯域幅が、ネットワークのパフォーマンスを左右する。
レイテンシは、パケットが宛先に届くまでの時間。
帯域幅は、1秒間で何ビットのデータを伝送できるかを示す。単位はbps

帯域幅の拡大については、まだまだ余地がある。
だがレイテンシの改善は違う。「光の速さ」という物理的な限界がある。光より速く通信することはできない。限界が存在する。

ラストマイル(ラストワンマイル)問題
レイテンシの増加は、ラストワンマイルで発生することが多い。

2章 TCP の構成要素

HTTP は仕様上、 UDP など TCP 以外のトランスポートプロトコルを使うこともできる。だが事実上、全ての HTTP トラフィックは TCP を使用している。
そのため、ウェブサイトのパフォーマンスを向上させるためには、TCP のメカニズムについて理解することが不可欠。

TCP は様々な機能を持っており、それにより、信頼性の高い通信が実現している。アプリケーションから複雑さを隠蔽してくれてもいる。だがそれが、パフォーマンスの悪化の原因にもなっている。TCP は、パフォーマンスよりも信頼性を重視するプロトコル。

全ての TCP 接続は、3 ウェイハンドシェイクで通信を開始する。
これが、パフォーマンスに影響を与える。通信開始時に必ず、3 ウェイハンドシェイクによるレイテンシが発生する。
そのため、TCP 接続の開始は高コストであり、接続の再利用が重要になる。

スロースタート、という問題もある。
輻輳制御のために、通信の開始時は少しずつしかセグメントを送信しない。送受信を繰り返すことで、少しずつ、一度に送信するセグメントを増やしていく。この、送信可能なデータ量を輻輳ウインドウサイズという。
そのため、ネットワークの性能的には 1 RTT で済むような通信が、それ以上の RTT を必要としてしまう。
これに対しては、TCP 接続の再利用や、送信するセグメントの初期値を増やすことが、有効。
このように、TCP の仕組みを理解することは、パフォーマンスを改善するために重要。

TCP はパケットの再送処理があるし、パケットが届く順序も保証されている。
だがそれが、パフォーマンスの低下をもたらす恐れがある。具体的にはHOL ブロッキング。これは、送信したパケットがロストした場合、そのパケットを再送するが、その間、後続のパケットは待機しないといけない。順序を守るため。ここで遅延が発生する。
そのため、信頼性が重要でないアプリケーションの場合は、UDP など他のトランスポートプロトコルを使ったほうがよいかもしれない。

ここまで見てきたように、TCP では、ハンドシェイクが発生するし、一度の通信での送信量も制限されることが多い。
そのため多くの場合、帯域幅ではなくレイテンシが、TCP のボトルネックになる。何度も通信が行われるし、帯域幅とは無関係にデータの送信量が制限されていることが多いため。

3章 UDP の構成要素

「パケット」と「データグラム」は、意味合いが異なる。
「データグラム」は、信頼性の低いサービスによって配信されるものを指す。IP や UDP が送信しているのは、データグラム。

UDP の特徴は、「どんな機能があるか」ではなく「どんな機能がないか」で、説明したほうがよい。
敢えて機能を除外していることが、UDP の特徴であり魅力。

DNS も、UDP を使っている。
WebRTC も使っている。WebRTC は、ブラウザ内で、P2P のリアルタイム通信を行う技術。

UDP は意図的にシンプルな仕組みになっているため、その上にアプリケーションを構築するためには、様々な機能を用意しないといけない。
NAT トラバーサルも、そのひとつ。UDP と NAT は相性が悪いため、様々な対応が必要になる。そのための仕組みが、NAT トラバーサル。
これらを実装する場合には費用対効果を検証するべきであり、多くの場合、既存のライブラリやフレームワークを使うことが望ましい。18 章で扱う WebRTC は、まさにそのようなフレームワークである。

4章 TLS

TLS は、TCP の直接の上位層で動作する。そのさらに上位のプロトコル(HTTP など)に影響を与えることなく、通信に安全性を提供する。

TLS は、以下の 3 つのサービスを提供する。

  1. 暗号化
    • 通信内容を暗号化する。
  2. 認証
    • 提供された証明書の正当性を検証する。
  3. データ整合性
    • メッセージの改竄や偽造を検出する。

ネットワーク上には様々な中間装置がある。
これらの中間装置は、HTTP 以外のプロトコルを利用しようとするときに、問題になるかもしれない。そのプロトコルを理解できなかったり、正しく対応できなかったり、エラーが発生したりする可能性があるから。
TLS は、このようなときにも役に立つ。通信内容を暗号化してしまうことで、中間装置からデータ内容を隠蔽してしまう。これなら、その中身がどんなものであっても中間装置は影響を受けないため、中間装置が対応していないプロトコルでも使うことが可能になる。

TLS は接続開始時にハンドシェイクを行うため、その分だけレイテンシが発生する。

HTTP には、プロトコルをアップグレードするための仕組みがある。
まず HTTP で通信を行い、ネゴシエートしてクライアントもサーバも対象のプロトコルに対応していた場合、そのプロトコルにアップグレードする。WebSocket や HTTP/2 がこれに該当する。
上述の中間装置の問題があるため、TLS を使って通信を行う。
アップグレードするためにはネゴシエートが必要であり、そのため 1 往復分のレイテンシが発生する。
ALPN(Application Layer Protocol Negotiation) は、TLS ハンドシェイクのなかで、プロトコルアップグレードのためのネゴシエートを行う機能。これにより、アップグレードのために必要だった 1 往復分のレイテンシを削減できる。

セッションキャッシング
TLS 接続に関する情報をクライアントとサーバの双方がキャッシュしておくことで、2 回目以降の TLS 通信の際にそれを再利用できる。
それにより、1 往復分のレイテンシを削減できる他、公開鍵暗号の処理で発生する計算コストも削減できる。
多くのブラウザは、この仕組みを利用している。つまり、最初の TLS 接続が完了するまで、2 回目以降の TLS 接続を意図的に待機する。そして、最初の接続が完了してから、そのキャッシュを利用する形で、2 回目以降の接続を行う。

セッションチケット(ステートレスなセッション再開)
クライアントが、すべてのセッションデータを保持する。このデータはサーバが持つ秘密鍵で暗号化されている。2 回目以降の接続では、このデータを使って TLS 通信を行う。
この仕組みなら、サーバ側がセッション情報をキャッシュしておく必要がなくなる。

TLS も、TCP や IP と同じように、プロトコルに従ってデータをフレーム化している。そのプロトコルを「TLS レコードプロトコル」という。
その作業は TLS 層が行うため、アプリケーションにとっては透過的。何かする必要はない。
だがパフォーマンスのことを考えるなら、レコードプロトコルについても把握しておく必要がある。アプリケーションのレコードサイズを適切に設定することで、レコードプロトコルによるオーバーヘッドや余計な処理を回避できるため。
レコードサイズが小さくても大きくても、それぞれの理由で、遅延を発生させる。そのため、TCP 接続の状態に応じて動的にレコードサイズを調整するのが、ベスト。

TLS False Start は、TLS ハンドシェイクが部分的に終わった時点でアプリケーションデータの送信を行う、TLS プロトコル拡張。
これを有効にすると、ハンドシェイクのためのパケットが 1 往復した時点で、アプリケーションデータを送信する。

証明書チェーンには余計な証明書を含めず、できるだけチェーンのサイズを小さくするに心がける。
証明書チェーンが長くなるとサイズが大きくなり、パケットの送信回数が増えてしまう。

openssl を使うことで、TLS ハンドシェイクやサーバの設定の検証を行える。

5章 ワイヤレスネットワーク入門

性質の異なる様々なワイヤレステクノロジーが存在する。WiFi、NFC、3G など。そのため、過度の一般化を行うのよくない。
だが、それらのワイヤレステクノロジーの多くは、今日つの原理原則に沿って動作している。パフォーマンスという観点から見ても、それは同様。なので、そういった基礎的な原理原則をまずは理解することが重要。

ユーザが体験すべき動作は、有線か無線かを問わない。アプリケーションは、接続の種類に関係なく正常に動作する必要がある。ユーザが接続の種類を意識しなければならないのは、間違っている。
だが開発者は、接続によって性質が異なることを意識した上で、アプリケーションを設計しなければならない。

ワイヤレスネットワークの大きな特徴は、共有媒体(電波)で通信が行われるということ。これが、パフォーマンスにも大きな影響を与える。

ワイヤレスネットワークのパフォーマンスを決定付ける重要な 2 つの要素が、帯域幅信号強度(SN比(signal-to-noise ratio))
割り当てられた帯域幅が大きいほど、データ転送速度も向上する。
共有媒体を使用する都合上、他のデバイスによる干渉を受ける可能性がある。干渉が強い状態でパフォーマンスを改善するためには、送信デバイスと受信デバイスの距離を縮めるか、出力を強めて信号強度を上げるかする必要がある。

帯域幅と信号強度が、ワイヤレスネットワークのパフォーマンスを決める主要な要素。
だが、変調アルゴリズムも、パフォーマンスに影響を与える。変調とは、0 と 1 のデジタル信号をアナログ信号(電波)に変換することであり、そのためのアルゴリズムが、変調アルゴリズム。変調アルゴリズムによって、効率が異なる。

ワイヤレスネットワークのパフォーマンスは、送信者と受信者の位置が少し変わっただけで大きく変動するし、他のデバイスの通信による干渉にも影響を受ける。そのため、計測するのが非常に困難。

6章 WiFi

WiFi の標準規格は IEEE 802.11 。

WiFi ネットワークは広く普及したが、それにより、ネットワークは混雑するようになった。その混雑が、パフォーマンスの悪化をもたらす。
複数のユーザが共有の電波を使っているため、競合が発生する。そしてそれは、レイテンシとスループットを悪化させる。

ワイヤレスユーザ間での衝突が発生すると、WiFi は再送処理を行う。
それはデータリンク層と物理層で行われ、ネットワークの上位層からは隠される。
そのため、TCP のパケットロスが増える、ということではない。

WiFi の帯域幅やレイテンシは動的であり、電波状況などによって大きく変動する。
そのため、レイテンシが重要なアプリケーションでは、WiFi ネットワーク利用時の挙動について、よく検討しておく必要がある。例えば WebRTC は UDP 上で動くため、レイテンシを低減できる。

7章 モバイルネットワーク

スマートデバイスの急速な普及は、それらのデバイスから利用できるネットワークやサービスに対する高い需要を生み出した。

規格の策定を行うグローバルな団体が 2 つある。3GPP と 3GPP2 。

モバイルネットワークの技術は、「3G」や「4G」などの世代で分類できる。だが「3G」や「4G」という技術が存在するわけではない。
各世代にはそれぞれ基準が設定されており、その基準を満たす技術が、各世代の技術と見做される。
まず、ITU(International Telecommunication Union) という団体が、データ転送速度やレイテンシなどのパフォーマンスの基準を設定する。各世代に対して。
そしてそれを満たす技術規格を、 3GPP と 3GPP2 が策定する。

4G については、ITU が 2008 年に策定した IMT-Advance という要求事項を全て満たした技術が、4G と呼ばれる。
以前の世代より高いスループットと低いレイテンシが求められる。
だが、基準を満たしていなくても、基準に限りなく近い技術は、4G を名乗れることになった。事実、LTE という技術は厳密には基準を満たしていないが、4G として市場に出ている。
そのため、今日「4G」と呼ばれているものは、かなり曖昧なものである。そのため、その裏で動いている実際の技術を理解することが大切。

LTE ネットワークは、既存の 3G ネットワークの単なるアップグレードではない。そのため、新しい無線ネットワークの開発が必要であり、巨額の資金が必要になる。移行にも時間がかかる。
そのため、HSPA+ のような既存の技術が LTE と共存する時代が続く。HSPA+ の開発は既存のインフラの漸進的なアップグレードで済むので、投資効率がよい。そして、既存のユーザもアップグレードの恩恵を受けることができる。

コストの回収と既存顧客のサポート、という都合もあるため、前世代のネットワークもしばらく維持される。
モバイルネットワーク向けのアプリを構築する場合は、そのことも念頭に置いておく必要がある。

3G や 4G は、RRC(Radio Resource Controller) という機能を持っている。
デバイスと無線基地局との間の全ての接続は RRC によって管理されており、どのタイミングで送信するのか、どの程度の強度の信号を利用するのか、といったことは全て RRC が管理している。
そして、RRC による管理は、制御できない。だからこそ、RRC がどのように動くのかを理解することが大切になる。

モバイルデバイスにとって、電波は多くの電力を消費する。しかもディスプレイと違って、常に維持しておかなければならない。
かといって、常に強い電波を維持していれば、バッテリーがすぐに無くなってしまう。しかし電波を弱くしすぎると通信が途切れ、再接続によるレイテンシが発生してしまう。
そして電波出力などは、RRC が管理している。だから、ハイパフォーマンスを実現するためには、RRC について理解することが不可欠となる。

デバイスの状態は RRC が制御している。
3GPP や 3GPP2 は、デバイスがどのような状態遷移を行うのかを、定義している。
細かい内容は規格や世代によって異なるが、アイドル状態と接続状態の 2 つが存在し、前者はネットワークを制御するためのトラフィックのみを待機し、後者はデータの送受信を行う。当然、前者のほうが省電力である。
ハイパフォーマンスと省電力の最適なバランスを実現するためには、この状態遷移の仕組みを理解する必要がある。

8章 モバイルネットワークの最適化

断続的なネットワークアクセスは、モバイルネットワークにおいてはアンチパターン。有線や WiFi においては問題ないが、モバイルネットワークでは、バッテリーの消費量を早めてしまう。それだけでなく、RRC による状態遷移が発生するため、レイテンシも生まれてしまう。
そのため、アクティブ状態まで送信を待機したり、アクティブ状態の際にまとめて送信したり、ポーリングではなくプッシュ配信を使用したりすべき。

9章 HTTP の歴史

HTTP がどのような歴史を辿ってきたのか、そしてその背景には何があるのか、といったことを知ることは重要。
なぜならそれが、HTTP におけるパフォーマンス戦略を理解するために必要な基礎知識になるから。

HTTP 0.9 では、HTML しか転送できなかった。
Web に対する需要の爆発的な増加に対応する形で生まれた HTTP 1.0は、HTML 以外にも、プレーンテキストや画像などを転送できるようになった。HTTP 1.0 では、リクエスト毎に TCP の新規接続を必要とする。
HTTP 1.1 では、重要なパフォーマンス最適化が取り入れられた。キープアライブ接続やパイプライン化など。
HTTP 2.0 は、低レイテンシ、高スループットを実現するために作られた。しかし、当面は HTTP 1.1 もサポートしなければならない状況が続くと思われる。そのため、HTTP 1.1のパフォーマンス最適化についても、知っている必要がある。

10章 Web パフォーマンス入門

Web の進化がもたらしたユーザ体験は 3 段階に分けることができる。

  1. ハイパーテキストドキュメント
    • 文字通りのドキュメントであり、これを取得するためには単一のリクエストを送ればよい。そのため、パフォーマンス最適化について考えるべき要素も少ない。その単一の HTTP リクエストを最適化するのみ。
  2. Web ページ
    • 単一のドキュメントではなく、ドキュメントと、その付属物(スタイルシートや画像など)によって構成される。そのため、複数のリクエストが発生する。パフォーマンスという観点から見ると、ドキュメントのロード時間ではなくページロード時間(PLT)が指標になった。
  3. Web アプリケーション
    • インタラクティブなアプリケーション。マークアップ、スタイルシート、スクリプトが、複雑な依存関係を持つ。パフォーマンスの指標として、PLT だけでは不十分になった。

Web アプリケーションはユーザに遅延を感じさせてはいけない。人間は、1 秒以上の遅延が発生すると、遅延を強く知覚する。

Web アプリケーションは、様々なホストに配置されている多くのリソースから構成されている。
そのため、Web アプリケーションをブラウザに表示する際、それらをダウンロードし、そしてレンダリングしなければならない。
そして、そのダウンロードはシーケンシャルにひとつずつ行われるのではないし、レンダリングも、全てのリソースのダウンロードを待つわけではない。そのため、DOM のレンダリングの開始と完了、そして全てのリソースの読み込み完了は、それぞれにタイミングが異なる。このことが、パフォーマンスの議論を複雑なものにしている。何をパフォーマンスの指標にすればいいのかが、自明ではない。アプリケーション毎に答えが異なるため、自分たちで考え、決めなければならない。

Web ブラウジングでは比較的小さなファイルを大量に取得するため、帯域幅よりもレイテンシが問題になる。

人工的テストは有用だが、実際のユーザのデータと組み合わせて、パフォーマンスを計測すべき。
実際のユーザのデータを計測することを RUM(Real-User Measurement) といい、その手段として Navigation Timing API などの API を使うことができる。

ブラウザは、パフォーマンス最適化のための様々な機能を実装しており、それらは自動的に行われる。
だが、最適化のためのヒントをブラウザに与えたり、ブラウザの機能を最大限活かすように実装を工夫したりすることで、最適化の効果をさらに高めることができる。

11章 HTTP 1.x

レイテンシの排除と低減、そして転送データ量の最小化。この 2 つがパフォーマンス最適化の基礎であり、不変の原則である。

HTTP でやり取りをするにはまず、TCP 接続を開始する必要がある。そして 2 章で見たように、TCP 接続開始時にはハンドシェイクが行われるため、1 RTT のレイテンシが発生する。
TCP 接続を再利用することでこのレイテンシを削減できるため、パフォーマンスの改善が見込める。10 章で見たように Web アプリケーションは多数のリソースで構成されておりリクエストが何回も発生するため、接続の再利用で得られる効果は大きい。
HTTP 1.1 ではキープアライブがデフォルトで有効になっているため、自動的に接続の再利用を試みる。

HTTP 1.1 には、パイプラインという機能も採用された。
これは、複数の HTTP リクエストを、レスポンスを待たずに送信するもの。こうすることで、サーバがアイドリング状態になることを防ぎ、効率的な通信を行えるようになる。これまでは、サーバはレスポンスを返したら次のリクエストがするまで待機になってしまい、無駄があった。
だがパイプラインには問題がある。HTTP 1.1 では、リクエストを受け取った順番で、レスポンスを返さないといけない。そのため、最初に受け取ったリクエストの処理に時間が掛かっている場合、後続のリクエストの処理が既に終わっているとしても、返すことができない。これを、HOL ブロッキングという。2 章で紹介した同名の現象と概念は似ているが、別のものなので注意。TCP と HTTP 、それぞれのレイヤーで HOL ブロッキングが存在する。
この問題のため、ほとんどのブラウザではパイプラインがデフォルトでは無効になっている。ただ、ブラウザ以外のクライアントでパイプラインを使う場合、条件を整えることができればパフォーマンスを大きく改善できる可能性もある。

多くのモダンブラウザは、ホストあたり 6 つまで、TCP 接続を並列利用できる。
この仕組みを利用したテクニックとして、ドメインシャーディングがある。
これは、リソースの配信をひとつのホストから行っていたものを、サブドメインを使って複数のホストから配信するようにすること。これにより、TCP 接続の並列数を増やすことができる。2 つのドメインから配信する場合は、最大で 12 の接続を並列利用できる。

HTTP ヘッダは無圧縮のプレーンテキストで送信されるため、高いオーバーヘッドにつながる恐れがある。
特に Cookie はヘッダの肥大化をもたらすので、注意が必要。

複数の JavaScript や スタイルシートをひとつにまとめたり、複数の画像を結合したりすることで、リクエスト数を減らすことができる。但し、キャッシュ戦略に悪影響を与えたり、レンダリングの開始やスクリプトの実行が遅れることでユーザ体験を悪化させたりする可能性もある。

12章 HTTP 2.0

HTTP 2.0 は、これまでの HTTP の基本的なコンセプトを何も変更しない。通信方法などは変更されたが、それはアプリケーションから隠蔽されている。そのため、既存のアプリケーションは何の対応もすることなく、そのまま HTTP 2.0 を利用できる。
だが、HTTP 2.0 を活用すれば、これまで不可能だった最適化を使用してパフォーマンスを改善していける。なので、積極的にその機能を利用すべき。

Google が開始した SPDY(スピーディー)というプロトコルが、HTTP 2.0 の原型。レイテンシを削減し、パフォーマンスの大幅な改善を図ることが目的だった。
SPDY は HTTP 2.0 と並行して開発が続けられ、HTTP 2.0 のための実験や検証の場として機能した。現在はその役割を終えている。

HTTP 2.0 は 1.x の代替ではなく、拡張である。そのため、基本的な文法や機能は変わらない。
にも関わらず 2.0 という「メジャーバージョンアップ」を行ったのは、データ交換方式が変わったため。新しい バイナリフレーミングレイヤー を定義しており、これは 1.x のサーバやクライアントと互換性がない。

HTTP 2.0 で通信されるメッセージは、バイナリフォーマットにエンコードして、送信される。
そのため、クライアントとサーバがお互いに、エンコードのメカニズムを理解していないといけない。そうしないと、通信が成り立たない。
バイナリフレーミングの分かりやすい例は HTTPS 。全ての HTTP メッセージがエンコード・デコードされ、やり取りされている。HTTP 2.0 も同じように動作する。

バイナリフレーミングを理解するためには、覚えるべき用語がいくつかある。

  • フレーム
    • HTTP 2.0 における通信の、最小単位。バイナリ。
  • ヘッダ
    • 各フレームはヘッダを持ち、そのヘッダには、フレームがどのストリームに所属するかという識別子を持っている。
  • メッセージ
    • 1 つ以上のフレームで構成される、HTTP メッセージ。
  • ストリーム
    • 双方向のフレームの流れ。1 つの接続に複数のストリームが存在することもある。

フレームは多重化されて送信され、ヘッダ内のストリーム識別子を元に、送信先で再構成される。
HTTP 2.0 では、HTTP メッセージは小さなフレームに分割され、それが単一の TCP 接続内で多数のストリームによって並列的に運ばれる。

この仕組みによってリクエストとレスポンスの多重化が実現し、HTTP における HOL ブロッキングは解決され、複数の TCP 接続を行う必要性もなくなる。
リクエストとレスポンスが多重化されることで、ドメインシャーディングのような、11 章で紹介した HTTP 1.x における最適化テクニックが不要になる。

ストリーム毎に優先度を設定することができる。しかし、HTTP 2.0 が提供しているのは、優先度付けを行うための仕組みのみ。どのように優先度を管理するかは、実装に依存する。

ストリーム毎のフロー制御の仕組みもある。これは、TCP のフロー制御と同様のもので、それをストリーム単位で行う。

HTTP 2.0 には、サーバプッシュという新機能がある。
これは、ひとつのリクエストに対して複数のレスポンスを返す機能。リクエストに対するレスポンスを返すが、それとは別に、追加のリソースを返す。
Web アプリケーションは複数のリソースで構成されている。そのため、例えばドキュメントをレスポンスする際に、そのなかで使われているスタイルシートやスクリプトも返してしまえば、レイテンシを削減できる。

HTTP 2.0 では HTTP ヘッダは圧縮されて送信されるため、パフォーマンスが向上する。

当面は、HTTP 1.x と 2.0 が共存する時代が続く。サーバやクライアントは両方をサポートしないといけない。
そのためクライアントは HTTP 2.0 を利用した通信の開始時に、通信相手が HTTP 2.0 に対応しているかを確認する必要がある。
まずは HTTP 1.x でリクエストを送り、リクエストヘッダにUpgradeフィールドを含めるなどしてネゴシエートを行う。サーバが HTTP 2.0 に対応していれば HTTP 2.0 を使った通信に移行し、対応していなければそのまま 1.x で通信を行う。

クライアントとサーバの両方が、新規のストリームを開始することができる。
クライアントが開始する場合はまず、HEADERSフレームを送信する。サーバが開始する場合はPUSH_PROMISEフレームを送信する。どちらのケースでもその後は、DATAフレームで HTTP メッセージの本文を送信する。
クライアントが開始したストリームの ID は奇数、サーバが開始した場合は偶数となり、ストリーム ID の衝突を防いでいる。

13章 アプリケーション配信最適化

とにかくレイテンシの排除や低減を行い、送信データを最小化することが、パフォーマンス戦略の基本。
この章では、それを実現するために実践すべき HTTP における具体的な手法について説明している。

リソースをキャッシュすることで不要なリクエストを削減できることはよく知られているが、意外と実践されていない。

画像に対して適切なフォーマット、適切なサイズ、適切な圧縮方法を選択することで、大きな効果を得られる。

HTTP 2.0 と 1.x では最適化戦略が異なる。1.x における様々なテクニックは、2.0 では逆効果になる恐れがある。そして 2.0 のテクニックは、当然 1.x では使えない。
しかし当面は、2.0 と1.x が並行して存在し続ける。そのため、2.0 と 1.x で同じアプリケーションコードを配信するのか、それともバージョン毎に異なるコードを配信するのか、といったことを考えなければならない。
プロトコルのバージョンに応じて適切な最適化を自動的に適用してくれるソフトウェアも存在するので、利用を検討する。

HTTP 2.0 の最適化のためには、ロードバランサやプロキシの選択や設定など、インフラ構成についても考える必要がある。

14章 ブラウザネットワーク入門

ブラウザは、ネットワークに関する様々な処理を自動的に行なってくれる。
最適化のための処理だけでなく、セキュリティのための処理も、自動的に行なってくれる。

そして、そのような低レイヤーの処理を自動的に行なってくれる一方、様々なアプリケーション API を提供している。
以降の章ではそれらを見ていく。

15章 XMLHttpRequest

XMLHttpRequest(XHR) によってブラウザで非同期通信が行えるようになり、インタラクティブな Web アプリケーションを構築できるようになった。
しかも、ブラウザが提供している API のため、低レイヤーで発生する様々な処理はブラウザが自動的に行なってくれる。開発者は、ビジネスロジックに集中できる。
エンコードやデコードについても、ブラウザが適切な処理を行ってくれる。

HTTP では、サーバからクライアントに対して新規接続を開始できない。そのため、サーバがクライアントに対してリアルタイムで通知を行う場合、ポーリングかストリームを使うことになる。
XML ではストリームの利用に制限があるため、ポーリングを使う。
ポーリングとは、定期的にリクエストを送信すること。リクエストを受け取ったサーバは、新しいデータがあればそれを返し、なければ空のレスポンスを返す。
ロングポーリングという手法もある。これは、リクエストを送信したクライアントはレスポンスが返ってくるまでアイドル状態になり、サーバは新しいデータが発生するまでレスポンスを返さない。レスポンスを受け取ったクライアントはリクエストを終了し、また新たなロングポーリングリクエストを発行する。
ロングポーリングでは、新しいデータが発生した時点ですぐにレスポンスが送信されるため、レイテンシが最小化される。しかも、ポーリングのように「空のレスポンス」も発生しないため、余計なリクエストやオーバーヘッドも削減できる。
データのアップデートが頻繁に発生する場合、ロングポーリングでは多数のリクエストが発生する。そのため、頻繁にアップデートが発生するがそれをリアルタイムに受け取る必要性が低い場合、ポーリングを使ってリクエスト数を減らしたほうがよいかもしれない。ポーリングでは、アップデートが実際に何回発生しているか関係なく、常に一定の間隔でリクエストとレスポンスが発生するため。

ロングポーリングと同様の機能は、16 章で扱う Server-Sent Events や 17 章で扱う WebSocket でも実現できる。そしてそれらのほうが、効率がよい。
だがロングポーリングは、XML をサポートしているブラウザ全てで利用できるという利点がある。そのため、より効率的な手法が使えない場合のフォールバックとして利用できる。

16章 Server-Sent Events

Server-Sent Events(SSE) を使うと、サーバからクライアントへの、テキストベースのデータストリーミングを実現できる。

クライアント側では、EventSourceオブジェクトを使って、接続の開始やコールバックの設定などを行う。
抽象化された API なので、低レベルの処理はブラウザが行ってくれる。

送信されるデータは、必ず UTF-8 のテキストデータ。バイナリデータを扱いたい場合は WebSocket を使う。

17章 WebSocket

WebSocket は、サーバとクライアント間で双方向のデータストリーミングを行う。テキストもバイナリも扱える。

クライアント側では、WebSocketオブジェクトを使って、接続の開始やコールバックの設定などを行う。

WebSocket の URL スキームはwswss。後者は TCP + TSL で暗号化されている。

WebSocket プロトコルは、メッセージがテキストかバイナリかを判断する。
だがそれ以上の情報は、プロトコルからは分からない。どのようなデータフォーマットを使っているのかは分からないし、HTTP ヘッダのような、メタデータをやり取りする仕組みもない。
そのため、クライアントとサーバが、共通のサブプロトコルを使ってデータを処理できるようにする必要が出てくる。
WebSocket はそのようなケースのために、ハンドシェイク時にサブプロトコルについてネゴシエーションできる仕組みを用意している。
const ws = new WebSocket(\ wss://example.com\ [\ protocol-1\ \ protocol-2\ ]);のように、接続開始時に、クライアントが利用できるサブプロトコルのリストを伝える。そしてサーバがその中から一つを選ぶ。
ネゴシエーションに成功するとonopenコールバックが、失敗するとonerrorコールバックが実行される。

ハンドシェイクには HTTP を利用できる。HTTP を利用することで、そのインフラを利用できる。
ハンドシェイクの際、専用のヘッダをいくつか利用して、サーバとクライアントがネゴシエーションを行う。
ハンドシェイクが完了したあとは、HTTP 通信は行わず、WebSocket プロトコルが通信を引き継ぐ。

18章 WebRTC

WebRTC(Web Real-Time Communication) は、ブラウザ間での通信やデータ共有を可能にする。

リアルタイム通信では、信頼性より適時性が重要になる。人間の脳は、動画や音声のデータが数コマ失われても気付かないが、遅延には敏感。
だからこそ、TCP ではなく UDP が使われている。
しかし UDP だけでは、WebRTC の要件を実現することはできない。様々なプロトコルや機能によって、WebRTC は構成されている。

TLS は UDP と組み合わせて使用することはできない。そのため、UDP を使っている WebRTC では、TLS の代わりに DTLS(Datagram Transport Layer Security) を使用する。
DTLS ハンドシェイクには 2 RTT が必要。
DTLS では、データの暗号化や、改竄や偽造が行われていないことの保証は行うが、認証は行わない。WebRTC クライアントは、自動的に自己署名証明書を生成し、それをハンドシェイクで使う。認証が必要な場合は、アプリケーションで実装しなければならない。

WebRTC においては、アプリケーションが配信の最適化を制御することはできない。それは、ブラウザが自動的に行なってくれる。SRTP や SRTCP といったプロトコルによって、ネットワークの状態に応じた動的な最適化を行ってくれる。

WebRTC では、音声データや動画データだけでなく、任意のアプリケーションデータを送信できる。そのために使用するプロトコルが SCTP
SCTP は TCP や UDP のような、トランスポートプロトコル。IP 上で直接動く。だが WebRTC では、SCTP は DTLS トンネル内でやり取りを行う。
TCP のようなフロー制御や輻輳制御の仕組みを持っている。そしてさらに、信頼性をどの程度保証するか、順序通りに配信するのか順不同で配信するのか、設定できる。
IP 上で SCTP を使うようにすれば、UDP は不要になるように思える。しかも HOL ブロッキングの問題も解決できる。しかし現実には、IP 上で SCTP を使うことはできない。ルータや NAT デバイスといった、ネットワーク上に存在する中間機器が、SCTP に対応していないためである。だから、例えば WebRTC では、DTLS で確立した通信経路のなかで SCTP を使っているのである。
逆に言えば、内部ネットワークのような、コントロール可能なネットワーク環境では、SCTP を使うことができる。

WebRTC を使うことで、P2P 通信の実現のために必要な様々な処理をブラウザが自動的に行なってくれる。
それでも、全てをブラウザに任せることはできず、開発者が考慮し、アプリケーションが実行しなければならない要素はある。特に多者間通信では、考えなければ要素が多い。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment