問題: 低レイテンシのクライアント サーバー接続とサーバー クライアント接続
ウェブは、HTTP のいわゆるリクエスト/レスポンス パラダイムを基盤として構築されています。クライアントがウェブページを読み込み、ユーザーが次のページをクリックするまで何も起こりません。2005 年頃、AJAX によってウェブのダイナミック性が向上し始めました。それでも、すべての HTTP 通信はクライアントによって制御され、サーバーから新しいデータを読み込むにはユーザー操作または定期的なポーリングが必要でした。
新しいデータが利用可能になったことをサーバー側が認識した瞬間に、そのデータをクライアントに送信できる技術は、かなり前から存在しています。これらの機能は「Push」や 「Comet」などの名前で呼ばれます。サーバーから開始された接続を装う最も一般的なハックの 1 つが、ロングポーリングです。ロングポーリングでは、クライアントはサーバーへの HTTP 接続を開き、レスポンスの送信まで開いたままにします。サーバーに新しいデータが実際に存在するたびに、レスポンスが送信されます(他の手法には、Flash、XHR マルチパート リクエスト、いわゆる htmlfiles が含まれます)。ロングポーリングなどの手法は非常に効果的です。Gmail チャットなどのアプリで毎日使用しています。
ただし、これらの回避策にはすべて 1 つの問題があります。HTTP のオーバーヘッドが発生するため、低レイテンシ アプリケーションには適していません。ブラウザでマルチプレーヤー ファースト パーソン シューティング ゲームをプレイしたり、リアルタイム コンポーネントを含む他のオンライン ゲームをプレイしたりすることを想像してみてください。
WebSocket の概要: ソケットをウェブに導入する
WebSocket 仕様では、ウェブブラウザとサーバー間の「ソケット」接続を確立する API が定義されています。簡単に説明すると、クライアントとサーバーの間で永続的な接続があり、両方の側がいつでもデータの送信を開始できます。
スタートガイド
WebSocket 接続を開くには、WebSocket コンストラクタを呼び出すだけです。
var connection = new WebSocket('ws://html5rocks.websocket.org/echo', ['soap', 'xmpp']);
ws:
に注目してください。これは WebSocket 接続の新しい URL スキーマです。安全な HTTP 接続に https:
が使用されるのと同様に、安全な WebSocket 接続に wss:
もあります。
一部のイベント ハンドラを接続にすぐに接続すると、接続が開かれたとき、受信メッセージが届いたとき、エラーが発生したときに知ることができます。
2 番目の引数には、オプションのサブプロトコルを指定できます。文字列または文字列の配列を指定できます。各文字列はサブプロトコル名を表し、サーバーは配列で渡されたサブプロトコルの 1 つだけを受け入れます。受け入れられるサブプロトコルは、WebSocket オブジェクトの protocol
プロパティにアクセスすることで確認できます。
サブプロトコル名は、IANA レジストリに登録されているサブプロトコル名のいずれかである必要があります。現在、2012 年 2 月時点で登録されているサブプロトコル名は 1 つだけです(soap)。
// When the connection is open, send some data to the server
connection.onopen = function () {
connection.send('Ping'); // Send the message 'Ping' to the server
};
// Log errors
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
};
// Log messages from the server
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
};
サーバーとの通信
サーバーへの接続が確立されるとすぐに(open
イベントがトリガーされるとすぐに)、接続オブジェクトの send('your message')
メソッドを使用してサーバーへのデータ送信を開始できます。以前は文字列のみをサポートしていましたが、最新の仕様ではバイナリ メッセージも送信できるようになりました。バイナリデータを送信するには、Blob
オブジェクトまたは ArrayBuffer
オブジェクトを使用します。
// Sending String
connection.send('your message');
// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
connection.send(binary.buffer);
// Sending file as Blob
var file = document.querySelector('input[type="file"]').files[0];
connection.send(file);
同様に、サーバーがいつでもメッセージを送信する可能性があります。この処理が実行されるたびに、onmessage
コールバックが呼び出されます。コールバックはイベント オブジェクトを受け取ります。実際のメッセージには data
プロパティからアクセスできます。
WebSocket は、最新の仕様でバイナリ メッセージを受信することもできます。バイナリ フレームは Blob
形式または ArrayBuffer
形式で受信できます。受信するバイナリの形式を指定するには、WebSocket オブジェクトの binaryType プロパティを「blob」または「arraybuffer」に設定します。デフォルトの形式は「blob」です。(送信時に binaryType パラメータを調整する必要はありません)。
// Setting binaryType to accept received binary as either 'blob' or 'arraybuffer'
connection.binaryType = 'arraybuffer';
connection.onmessage = function(e) {
console.log(e.data.byteLength); // ArrayBuffer object if binary
};
WebSocket に新たに追加された機能として、拡張機能があります。拡張機能を使用すると、フレームを圧縮したり、多重化したりできます。サーバーが受け入れる拡張機能は、オープン イベントの後に WebSocket オブジェクトの extensions プロパティを調べることで確認できます。2012 年 2 月現在、拡張機能の仕様は正式に公開されていません。
// Determining accepted extensions
console.log(connection.extensions);
クロスオリジン通信
WebSocket は最新のプロトコルであるため、クロスオリジン通信が組み込まれています。信頼できるクライアントとサーバーとの通信のみを行う必要がありますが、WebSocket を使用すると、任意のドメインの間で通信できます。サーバーは、サービスをすべてのクライアントに公開するか、明確に定義された一連のドメインに存在するクライアントにのみ公開するかを決定します。
プロキシ サーバー
新しいテクノロジーが登場すれば、新しい問題が伴います。WebSocket の場合、ほとんどの企業ネットワークで HTTP 接続を仲介するプロキシ サーバーとの互換性です。WebSocket プロトコルは、HTTP アップグレード システム(通常は HTTP/SSL に使用)を使用して、HTTP 接続を WebSocket 接続に「アップグレード」します。一部のプロキシ サーバーはこれを許容せず、接続を切断します。したがって、特定のクライアントが WebSocket プロトコルを使用していても、接続を確立できない場合があります。そのため、次のセクションがさらに重要になります。
WebSocket を今すぐ使用
WebSocket はまだ新しい技術であり、すべてのブラウザで完全に実装されているわけではありません。ただし、WebSocket を使用できない場合は、上記のいずれかのフォールバックを使用するライブラリで WebSocket を使用できます。この分野で非常に人気のあるライブラリは socket.io です。このライブラリには、プロトコルのクライアント実装とサーバー実装が付属しており、フォールバックも含まれています(2012 年 2 月現在、socket.io はバイナリ メッセージをサポートしていません)。PusherApp などの商用ソリューションもあります。WebSocket メッセージをクライアントに送信する HTTP API を提供することで、任意のウェブ環境に簡単に統合できます。追加の HTTP リクエストが原因で、純粋な WebSocket と比較して常に追加のオーバーヘッドが発生します。
サーバーサイド
WebSocket を使用すると、サーバーサイド アプリケーションのまったく新しい使用パターンが生まれます。LAMP などの従来のサーバー スタックは HTTP リクエスト/レスポンス サイクルを中心に設計されているため、多くのオープン WebSocket 接続を適切に処理できないことがよくあります。多数の接続を同時に開いたままにするには、パフォーマンスのコストが低く、同時実行性が高いアーキテクチャが必要です。このようなアーキテクチャは通常、スレッド処理またはいわゆるノンブロッキング IO を中心に設計されます。
サーバーサイドの実装
- Node.js
- Java
- Ruby
- Python
- Erlang
- C++
- .NET
プロトコルのバージョン
WebSocket のワイヤー プロトコル(ハンドシェイクとクライアントとサーバー間のデータ転送)は RFC6455 になりました。最新の Chrome と Android 版 Chrome は、バイナリ メッセージングを含む RFC6455 に完全に準拠しています。また、Firefox はバージョン 11、Internet Explorer はバージョン 10 に対応しています。古いプロトコル バージョンは引き続き使用できますが、脆弱性が知られているため、おすすめしません。古いバージョンの WebSocket プロトコルのサーバー実装がある場合は、最新バージョンにアップグレードすることをおすすめします。
ユースケース
クライアントとサーバー間の低レイテンシのニア リアルタイム接続が必要な場合は、WebSocket を使用します。イベントキューなどのテクノロジーに重点を置いて、サーバーサイド アプリケーションの構築方法を再考する必要がある場合があります。ユースケースの例:
- マルチプレーヤー オンライン ゲーム
- チャット アプリケーション
- ライブ スポーツのニュース速報
- リアルタイムで更新されるソーシャル ストリーム