問題: 低レイテンシのクライアント サーバー接続とサーバー クライアント接続
ウェブは、HTTP のいわゆるリクエスト / レスポンス パラダイムを中心に構築されてきました。クライアントがウェブページを読み込むと、ユーザーが次のページをクリックするまで何も起こりません。2005 年頃、AJAX によってウェブはより動的なものになりました。それでも、すべての HTTP 通信はクライアントによって制御されていたため、サーバーから新しいデータを読み込むには、ユーザー操作または定期的なポーリングが必要でした。
新しいデータが利用可能になったことをサーバーが認識した瞬間に、サーバーがクライアントにデータを送信できるようにするテクノロジーは、かなり前から存在しています。「Push」や「Comet」などの名前で呼ばれています。サーバーが接続を開始しているように見せるための最も一般的なハックの 1 つが、ロング ポーリングです。ロング ポーリングでは、クライアントがサーバーへの HTTP 接続を開き、レスポンスを送信するまで接続を開いたままにします。サーバーに実際に新しいデータがある場合は、レスポンスを送信します(他の手法には、Flash、XHR マルチパート リクエスト、いわゆる htmlfiles があります)。ロング ポーリングなどの手法は非常に効果的です。Gmail のチャット機能などのアプリケーションで毎日使用しています。
しかし、これらの回避策にはすべて、HTTP のオーバーヘッドを伴うため、低レイテンシのアプリケーションには適していないという問題があります。ブラウザでプレイするマルチプレーヤー型のファーストパーソン シューティング ゲームや、リアルタイム コンポーネントを備えたその他のオンライン ゲームを考えてみてください。
WebSocket の概要: ウェブにソケットを導入する
WebSocket 仕様では、ウェブブラウザとサーバー間の「ソケット」接続を確立する API が定義されています。簡単に言うと、クライアントとサーバーの間に永続的な接続があり、どちらもいつでもデータの送信を開始できます。
スタートガイド
WebSocket 接続を開くには、WebSocket コンストラクタを呼び出すだけです。
var connection = new WebSocket('ws://html5rocks.websocket.org/echo', ['soap', 'xmpp']);
ws: に注目してください。これは、WebSocket 接続の新しい URL スキーマです。安全な WebSocket 接続には wss: もあります。これは、安全な HTTP 接続に https: が使用されるのと同じ方法です。
一部のイベント ハンドラを接続にすぐにアタッチすると、接続が開かれたとき、着信したメッセージを受信したとき、エラーが発生したときを把握できます。
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 に新たに追加されたもう 1 つの機能は拡張機能です。拡張機能を使用すると、圧縮、多重化されたフレームを送信できます。サーバーが受け入れる拡張機能を確認するには、open イベントの後に 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 です。このライブラリには、プロトコルのクライアントとサーバーの実装が付属しており、フォールバックが含まれています(socket.io は 2012 年 2 月の時点ではバイナリ メッセージをサポートしていません)。また、PusherApp などの商用ソリューションもあります。これは、WebSocket メッセージをクライアントに送信する HTTP API を提供することで、あらゆるウェブ環境に簡単に統合できます。追加の HTTP リクエストがあるため、純粋な WebSocket と比較すると常に余分なオーバーヘッドが発生します。
サーバーサイド
WebSocket を使用すると、サーバーサイド アプリケーションのまったく新しい使用パターンが作成されます。LAMP などの従来のサーバー スタックは HTTP リクエスト / レスポンス サイクルを中心に設計されていますが、多くの場合、多数のオープン WebSocket 接続を適切に処理できません。多数の接続を同時に開いたままにするには、低いパフォーマンス コストで高い同時実行性を実現するアーキテクチャが必要です。このようなアーキテクチャは通常、スレッドまたは、いわゆる非ブロッキング IO を中心に設計されています。
サーバーサイドの実装
- Node.js
- Java
- Ruby
- Python
- Erlang
- C++
- .NET
プロトコル バージョン
WebSocket のワイヤ プロトコル(クライアントとサーバー間のハンドシェイクとデータ転送)は RFC6455 になりました。最新の Chrome と Chrome for Android は、バイナリ メッセージングを含む RFC6455 に完全に対応しています。また、Firefox はバージョン 11、Internet Explorer はバージョン 10 に対応します。古いプロトコル バージョンも引き続き使用できますが、脆弱性が確認されているため、おすすめしません。古いバージョンの WebSocket プロトコルをサーバーに実装している場合は、最新バージョンにアップグレードすることをおすすめします。
ユースケース
クライアントとサーバー間で、真に低レイテンシでニア リアルタイムの接続が必要な場合は、WebSocket を使用します。この場合、イベントキューなどのテクノロジーに新たに焦点を当てて、サーバーサイド アプリケーションの構築方法を再考する必要があるかもしれません。たとえば、次のようなユースケースが考えられます。
- マルチプレーヤー型オンライン ゲーム
- Chat アプリケーション
- ライブ スポーツ ティッカー
- リアルタイムで更新されるソーシャル ストリーム