問題: 低レイテンシのクライアント サーバー接続とサーバー クライアント接続
ウェブは、HTTP のいわゆるリクエスト / レスポンス パラダイムを基盤として構築されています。クライアントがウェブページを読み込み、ユーザーが次のページをクリックするまで何も起こりません。2005 年頃、AJAX によってウェブのダイナミック性が向上し始めました。それでも、すべての HTTP 通信はクライアントによって制御され、サーバーから新しいデータを読み込むにはユーザー操作または定期的なポーリングが必要でした。
新しいデータが利用可能になったことをサーバー側が認識した瞬間に、そのデータをクライアントに送信できる技術は、かなり前から存在しています。これらの機能は「Push」や 「Comet」などの名前で呼ばれます。サーバーが接続を開始したと錯覚する最も一般的なハッキングは、ロング ポーリングと呼ばれます。ロング ポーリングでは、クライアントはサーバーに対して 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 を使用します。その際には、イベントキューなどのテクノロジーに着目して、サーバーサイド アプリケーションの構築方法を再考する必要があるかもしれません。ユースケースの例を次に示します。
- マルチプレーヤー オンライン ゲーム
- チャット アプリケーション
- ライブ スポーツのニュース速報
- リアルタイムで更新されるソーシャル ストリーム