問題: 待ち時間の少ないクライアント/サーバー接続とサーバー/クライアント接続
ウェブは主に、HTTP のいわゆるリクエスト/レスポンス パラダイムに基づいて構築されてきました。クライアントがウェブ ページを読み込んだ後は、ユーザーが次のページをクリックするまで、何も起こりません。2005 年頃、AJAX によって、ウェブがよりダイナミックに感じられる方向への動きが始まりましたが、それでもクライアントでは HTTP 通信が全面的に使用され、サーバーから新しいデータを読み込むためにユーザー操作や定期的なポーリングを必要としました。
新しいデータが利用可能になった時点でサーバーがクライアントにデータを送信できるようにするテクノロジーは、かなり前から存在していました。このようなテクノロジーは、「Push」や「Comet」という名前で知られています。サーバーが接続を開始したように見せる最も一般的なハックは、long polling と呼ばれます。long polling では、クライアントはサーバーへの HTTP 接続をオープンし、サーバーは応答を送信するまでその接続を維持します。サーバーに実際に新しいデータがある場合、サーバーは応答を送信します(他のテクニックとして、Flash、XHR multipart リクエストのほか、いわゆる htmlfiles があります)。long polling とその他のテクニックは一緒に使用できます。これらは、GMail チャットなどのアプリケーションで日常的に使っているテクニックです。
ただし、これらすべての対処方法には共通する問題が 1 つあります。HTTP のオーバーヘッドがあるため、待ち時間の少ないアプリケーションには最適と言えません。ブラウザで実行するマルチレイヤーの一人称シューティング ゲームや、リアルタイム コンポーネントを使用するその他のオンライン ゲームを考えてみてください。
WebSocket の導入: ウェブにソケットを実装する
WebSocket 仕様は、ウェブ ブラウザとサーバー間に「ソケット」接続を確立する API を定義しています。簡単に言うと、クライアントとサーバーの間に持続的接続があり、どちらの側からでも、いつでもデータの送信を開始できます。
はじめに
WebSocket 接続を開くには、単に WebSocket コンストラクタを呼び出します。
var connection = new WebSocket('ws://html5rocks.websocket.org/echo', ['soap', 'xmpp']);
ws: に注目してください。これは WebSocket 接続用の新しい URL スキーマです。セキュリティで保護された WebSocket 接続用の wss: もあります。これは、https: がセキュリティで保護された HTTP 接続に使用されるのと同じです。
いくつかのイベント ハンドラを接続に直接アタッチすると、接続が開かれたか、受信メッセージを受け取ったか、エラーが発生したときに、通知を受けることができます。
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 オブジェクトの拡張機能プロパティを調べることにより、サーバーが受け入れた拡張機能を確認できます。2012 年 2 月時点で、正式に発行された拡張機能の仕様はありません。
// Determining accepted extensions console.log(connection.extensions);
Cross Origin 通信
最近のプロトコルとして、cross origin 通信が 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 を用意することによって、任意のウェブ環境に簡単に統合できます。純粋な WebSocket に比べて HTTP リクエストが増えるため、常に追加的なオーバーヘッドが存在します。
サーバー サイド
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 を使用してください。そのためには、サーバー サイド アプリケーションを構築する方法を、イベント キューなどのテクノロジーに新たな重点を置いて考え直すことが必要な場合があります。次のような使用例があります。
- マルチレイヤーのオンライン ゲーム
- チャット アプリケーション
- 最新のスポーツ情報を伝えるティッカー
- ソーシャル ストリームのリアルタイム更新
デモ
- Plink
- Paint With Me
- Pixelatr
- Dashed
- Massively multiplayer online crossword
- Ping server(上記の例の中で使用されています)
- HTML5demos サンプル