tech.guitarrapc.cóm

Technical updates

リアルタイム通信で利用されるプロトコルと手法

NOTE: 本記事はすでに内容が古く、今読んでも役に立つ度合いはほぼないです。

本記事は、先日社内勉強会のために準備した、Webサービスのリアルタイム通信周りのまとめシリーズ の1つを転載して公開するものです。

まだまだわかっていないことが多いので、ぜひぜひ間違っている点などにご指摘いただければと思い公開します。

ぜひぜひ優しくマサカリをいただけると泣いて喜びます!

目次

はじめに

ここでは、Webサービスのリアルタイム通信に利用されるプロトコルと手法をざっくり説明します。ライブラリについては別項目で!

リアルタイム通信に利用される手法がいくつかありますが、それぞれ意図していることが違います。

プロトコルと手法

ざっくり見てみましょう。

名称 プロトコル RFC データ取得時の再接続 サーバープッシュ メモリ消費 遅延の少なさ デスクトップでの利用
Polling HTTP X X X 容易
LongPolling(Comet) HTTP RFC6202 O 容易
Server Sent Events HTTP W3C CR 不要 O O O ブラウザ互換依存あり
Forever-frame HTTP(iframe) - 不要 O O 容易
WebSocket WebSocket RFC 6455 不要 O O 面倒
RUDP RUDP IETF Draft (標準化なし) O ? Photon使えば

前世代のやり方であるComet について

従来の Polling (後述) や Ajax では、サーバーからクライアントにデータを送る(PUSH) するのが困難でした。そこで、提唱されたのが Comet で、「サーバーでイベント更新された時にクライアントにデータをプッシュするアプリケーションモデル」を指します。

とはいえ、Comet では サーバーからレスポンスが返されたら接続を切る(この文脈は主に LongPolling を指してます) ため、真の意味でリアルタイム性にはまだ一歩足りません。そして Comet は Server Sent Events、 Forever frame、 WebSocket が出てきて オワコン感とかいわれてる...。

とりあえずPolling系と Streaming 系があるのでざくっと。

Polling 系

「サーバーに更新がきたら教えて」と、クライアントからリクエストなげておいて、サーバーは更新があったらレスポンスを返す手法

  • LongPolling

Streaming 系

サーバーは更新イベントがあったら、「接続されているクライアントに更新を通知」する手法

  • XMLHttpRequest (ここでは扱いません)
  • XHR streaming (ここでは扱いません)

過渡期といわれてる手法

  • Forever-frame (Comet の一部として言われることもある)
  • Server Sent Events (LongPolling の発展版感覚。 Chunked Transfer Coding をまとも使えるようにした感)

将来有望といわれてる手法

各種ライブラリで広く用いられており、名前を良く知られているのが WebSocket です。

  • WebSocket
  • RUDP (Reliable User Datagram Protocol)

WebSocket は、WebSocketプロトコルを用いることで、これまでのHTTPの発展ではできない双方向通信を実現しているのが特徴です。

では、リアルタイム通信で利用されるプロトコル/手法を順に見ていきましょう。

Polling

名称 プロトコル RFC データ取得時の再接続 サーバープッシュ メモリ消費 遅延の少なさ デスクトップでの利用
Polling HTTP X X X 容易

クライアントからサーバーへの PULL型通信です。

Pollingは、クライアントが主導してサーバーに情報を送信する時に真っ先に思いつく汎用的な手法です。仕組みは単純で、クライアントからサーバーに定期的に問い合わせを行うことで、サーバー上に更新が発生していないか確認し更新があれば取得します。

メリット

単純なため、実装が容易で応用範囲も広いです。HTTP の基本的な機能を使用しているのみのため、Web、デスクトップ、ネイティブと利用シーンも問いません。

デメリット

サーバーに更新があるか毎回確認しにいくため、通信コストの大半は無駄になります。ネットワーク通信は様々な処理の中でもコストが大きいため、バッテリー消費、帯域幅、通信の両面で代償が大きいため好ましくありません。

特に、接続を生成されるタイミングはサーバーにとって馬鹿にならないコストがかかります。これが多数のクライアントからとすると?死亡一直線です!

リアルタイム性を向上しようと思うと、ポーリング間隔を短くする必要がありますが、それは更なるクライアント、サーバーのリソース圧迫をもたらします。ぐぬぬん!

対策として、アダプティブインターバルピギーバッキング という緩和策があります。いずれもリソースを効率良くすることを主眼においた手法です。

向いているシーン

以下のシーンでは妥当といえます。

  • あまり更新が頻繁ではない
  • サーバーからクライアントにプッシュ通信する必要がない

Long Polling (Comet)

名称 プロトコル RFC データ取得時の再接続 サーバープッシュ メモリ消費 遅延の少なさ デスクトップでの利用
LongPolling HTTP RFC6202 Comet(Polling) 容易

ポーリングと同様に、クライアントからサーバーへの PULL型通信です。

Polling の発展版

Pollingでは、サーバーはクライアントからリクエストがあったら速やかにレスポンスを返していました。

LongPolling では、「クライアントからのリクエストを、サーバーがPUSHしたい時まで返さず保持する」ことで、リアルタイムに近い応答ができるようにします。

クライアントがサーバーにリクエストをしに行くとサーバーはそのリクエストを保持します。サーバーは、何かサーバー側で変更が発生したらクライアントにレスポンスを返ります。

リクエストをポーリングに比べて長く保持することで、接続、切断の回数大きく省いてリアルタイムに近い挙動を実現しています。

メリット

ポーリングに比べて、リアルタイミング性が圧倒的に高くなります。また、ポーリングが圧倒的に長く少なくなります。やったね!

さらに、サーバーに更新があったらレスポンスを返すので、「疑似的」にサーバーからクライアントへのイベントを発信(PUSH型の通信)ができます。 そのため LongPolling は双方向通信の手段の1つに数えられます。

デメリット

一度PUSHしたら、クライアントからのリクエストを待たないといけないので、極小間隔での連続PUSH通信はできません。

データ送信のたびに接続を再生成するのでメモリ消費が少し大目です。また、他のリアルタイム通信と比較すると、接続が若干遅いことが挙げられます。繋ぎっぱなしじゃないので仕方にゃい..。

接続が遅いということは、通知と通知で遅延が気になるケースもありえます。特に、サーバーがレスポンスを返している最中に発生したイベントは、次にクライアントがリクエストしてくるまで遅延します。

対策として、LongPolling は HTTP が提供する機能だけ使用しているので、同じタイミングのメッセージは1つのHTTPレスポンスにまとめるなどが有効です。

LongPolling 自体は双方向通信ではない

みてのとおり LongPolling はサーバーからクライアントへの接続に利用されるため、クライアントからサーバーへの通信を行いたい場合は、別途HTTP(S)接続を作る必要があります。

  • サーバーからクライアントへの通信には LongPolling
  • クライアントからサーバーへの通信には 通常の HTTP(S)

接続が閉じられるケース

接続はクライアントから常に開かれ、接続が閉じられるのはいずれかのケースです。どちらのケースでもすぐに新しい接続が確立され、データ更新を待ち受けます。

  • サーバーがその接続でクライアントにデータを送信する
  • 接続がアイドル状態になりタイムアウトが発生する

向いているシーン

以下のシーンでは妥当といえます。

  • Polling ではちょっと頻繁に接続ありすぎる場合
  • Server Sent Events に対応していないブラウザでリアルタイム性のある通信をしたい場合

Server Sent Events

名称 プロトコル RFC データ取得時の再接続 サーバープッシュ メモリ消費 遅延の少なさ デスクトップでの利用
Server Sent Events HTTP W3C CR 不要 Commet(Streaming) O O ブラウザ互換依存あり

LongPolling の延長と考えるとイメージしやすいでしょう。

LongPolling の発展版

まず、クライアントはサーバーに接続する際に、Content-Type に text/event-stream を指定します。すると、サーバーはイベント更新が発生したら、クライアントに Chunked (断片) データとしてレスポンスを送信します。LongPolling との違いはここで、 Chunked としてレスポンスの一部を送信することでクライアントとの通信は切れず、再リクエストをする必要がありません。

これにより、LongPolling までの 「クライアント契機のPULL型通信」から、「サーバー契機のPUSH型通信」に発展しています。リアルタイム通信はPUSH型でないと遅延を回避できないのでダイジダイジ。

メリット

LongPolling に比べて、よりリアルタイム性が高まります。LongPolling にあった、レスポンス後の再リクエストというコストがなく遅延も少なくなっています。

再接続コストもないので、メモリ消費も優し目。

サーバー側の実装追加がほぼ不要。WebSocket の場合は、従来の実装から変更が必要になる。

リトライや Keep-alive も仕様に含まれてるのでお任せできる。

デメリット

クライアント側のブラウザなどが、Server Sent Events に対応していないと使えない! (IE10 でもだめぽよ。Android 4.4 未満の標準ブラウザもダメぽよ)

WebSocket と違い HTTP なのでちょっと効率悪い。

セキュリティモデルも HTTP 引き継いじゃってるので、場合によってはめんどー。

ブラウザ側の実装に引きずられることもままあります。 (kyo_ago さんがバグ多いっていうレベル)

\r\n 区切りで送信するので、バイナリはむりぽよ。

LongPolling もそうですが、サーバープッシュ用途でコネクションを貼り続けるため、クライアントからサーバーにデータを送るためには別コネクションを張らないとだめ。

向いてるシーン

ブラウザが対応していて、既存の実装をあまり変更せずにやりたいならWebSocket より簡単。基本はHTTP、XMLHTTPRequest の改善がメインなので、WebSocket が使えない時の選択に。

WebSocket と違い、HTTP 1.1 なので Proxy も通りやすい。(一部 chunked stream 通してくれないプロキシもあるけど!)

HTTP での開発経験を生かせる。

死んでく技術扱いされてる(要出典) ので、WebSocket使えるならムシ!

備考

Android 2系で使うなら、1回の送信が 4K 超えるように padding 入れるなりしましょう。

Forever-frame

名称 プロトコル RFC データ取得時の再接続 サーバープッシュ メモリ消費 遅延の少なさ デスクトップでの利用
Forever-frame HTTP(iframe) - 不要 O O 容易

HTML の iframeタグを使って常時接続を実現する方法です。ざっくりクライアントが開くHTML に、サーバーのURLをソースとした iframe タグを埋め込みます。あとはこの接続を開いたままにして、クライアント側で定義された関数を呼びだすことでデータの送信を行います。

メリット

接続が開きっぱなしなので、リソースの無駄は小さい。

HTML + JavaScript + HTTP を駆使すれば、だいたいどういうシーンでも使える。けど、ブラウザとかみたいに標準でサポートしていない環境で使うのは一般的ではない。

デメリット

1つの接続は片方向通信のため、サーバーからクライアントへの Forever-frame 接続を張ったら、クライアントからサーバーへの通信には別にHTTP接続を作る必要があります。

また、HTML + JavaScript + HTTP を駆使しているため、デスクトップアプリケーション向きかというとちょっとつらさあるです。

クライアント内部のインメモリにレスポンスをためていくので、メモリ消費が優しくない + 定期的なリサイクルを考慮する必要があります。

向いてるシーン

あれ、なんかつらさが多い。。。。ブラウザ依存が小さいので、まぁ使えるシーンで。

WebSocket

名称 プロトコル RFC データ取得時の再接続 サーバープッシュ メモリ消費 遅延の少なさ デスクトップでの利用
WebSocket WebSocket RFC 6455 不要 O O 面倒

サーバーとクライアント間で、双方向通信をおこなうために提唱された通信規格の1つです。

クライアントはサーバーと一度 HTTPで接続を確立(ハンドシェイク)させたら、Upgrade ヘッダを用いて HTTP1.1 からWebSocket に切り変えて以降は繋ぎっぱなしします。これにより、「クライアントからサーバーへの送信」、「サーバーからクライアントへの送信」と双方向に通信が可能となります。他のが、サーバーからの送信と、サーバーへの送信でそれぞれ通信チャンネルを作っていたのと異なります。

メリット

HTTPに比べて軽量なヘッダで、通信コストは低いです。

通信コストが低いということは、よりリアルタイム性が高く負荷も低めです。

バイナリも送れるので、Server Sent Events と違ってこの辺は扱いが楽。

サーバー と クライアントで同じ通信経路を使うため、これまで紹介したようなクライアントからサーバーへ投げる時は別経路を用意する必要はありません。(あくまでその WebSocket接続で担保する通信に限るのは当然ですが)

クライアントからサーバー側の関数呼び出し、サーバー側からクライアント側の関数呼び出しを実装するライブラリが多いのも特徴です。例えば、SignalR では、サーバー側の Hubメソッドを クライアントの JavaScript から呼びだしたり、サーバー側からクライアント側の JavaScript メソッドを呼び出したりできます。

デメリット

サーバー、クライアント共に WebSocket に対応している必要があります。(IE10未満、Android 4.4 未満の標準ブラウザとか)

既存の通信機器がUpgrade ヘッダを通せない可能性がある。(https でくるむと、中身見えないのでいけることがおおいにゃ!)

向いてるシーン

ブラウザや環境がサポートしてるなら、Server Sent Events や Forever-frame よりリアルタイム性が高く、低負荷です。

RUDP

名称 プロトコル RFC データ取得時の再接続 サーバープッシュ メモリ消費 遅延の少なさ デスクトップでの利用
RUDP RUDP IETF Draft (標準化なし) O ? O Photon使えば

HTTP や WebSocket で利用されている TCP は、信頼性が高い代わりにオーバーヘッドが大きいです。一方で、UDPはオーバーヘッドが小さく、軽量、高速な代わりに信頼性が低いです。

プロトコル メリット デメリット
UDP 高速、軽量 確実にパケットが届けられた保証、信頼性がない
TCP パケット到達の保障、信頼性が高い 低速、重い

そこでベル研が独自OSで利用するために、UDP を改良したプロトコルが RUDP (Reliable User Datagram Protocol) です。RUDPは Reliable を足した別実装は多いのですが、ここではPhoton で利用されてるのに限定しようかにゃ。

いずれも TCP 同様に信頼性を持たせる実装を、TCPよりも低いオーバーヘッドで満たしています。

  1. 受信パケットの確認応答
  2. ウィンドーイングとフロー制御
  3. 欠損パケットの再送
  4. リアルタイムストリーミングより速いオーバーバッファリング
プロトコル メリット デメリット
RUDP 高速、軽量、パケット送信の保証 標準化されておらず一般的じゃない + 専用フレームワークで扱うことになる

逆にいうと、これまで見てきたWebSocket のような 「サーバーとクライアントが繋ぎっぱなし」というよりは、RUDP で次々にパケットを送りつけあう仕組みといえるかも。

ぶっちゃけ RUDP 自体に関しては詳細ふめーです。ドキュメントみつけられにゃい.... 公開情報足りなすぎ...。

メリット

TCP よりも圧倒的に高速、コスト低い。

デメリット

RUDP をハンドリングするライブラリ、というよりもはやフレームワークないと低レイヤすぎ。(セッション層扱いたくないぉ)

向いてるシーン

Photon つかってくれ~。おわり。