今回はWebSocketを取り巻く技術的課題や、実装状況についてご紹介します。また私たちが開発したサービス「Pusher」についても取り上げます。
前回のWebSocketの紹介を読んでくださった読者のみなさんはWebSocketを試してみたくてたまらないのではないでしょうか。でも少し待ってください。皆さんを脅かす訳ではありませんが、以下の点についても考えなければいけません。
IETFのドラフトを見てみると、最初のバージョン(00)は2009年の1月に策定されたのが分かります。Chromiumが最初にWebSocketの実装を発表したころのバージョンは66の辺りです。
それからしばらくの間はマイナーチェンジばかりだったのか、あまりバージョンの変更が話題になることはなかったのですが、2010年5月ごろに激震が走りました。私が勝手にですが「ドラフト76問題」と呼んでいるものです。
以下は1つ前のドラフト75のリクエストです。前回紹介したリクエストに比べてぐっとシンプルだったのが分かると思います。WebSocketと分かるのは「Upgrade: WebSocket」と「WebSocket-Protocol: sample」のところぐらいだと思います。
GET /demo HTTP/1.1 Upgrade: WebSocket Connection: Upgrade Host: example.com Origin: http://example.com WebSocket-Protocol: sample
これがドラフト76では「Sec-」という接頭辞がいくつかのヘッダーフィルドに付くとともに、以下の2つの「キー」フィールドとHTTPボディの部分にも8バイトのランダムな文字が付きました。
Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 ^n:ds[4U
サーバサイドの方ではこの3つの情報をもとにレスポンスに16バイトの以下のような文字列を戻す必要があります。
8jKS'y:G*Co,Wxa-
これによって今までのドラフトとの互換性が損なわれることになりました。
また、今まで「draft-hixie-thewebsocketprotocol-75」と作者Ian Hicksonさんの名字が入っていたドラフトは「draft-ietf-hybi-thewebsocketprotocol-00」と新しくieftの文字が入ったバージョンとして番号もリセットされました(ここの00がドラフト76と同じバージョンになります)。
そして最後に重要な点がChromiumチームのブログポストにあります。
The WebSocket protocol is still actively being changed. Until there is more consensus, we will continue to update our implementation to follow the latest draft of specification, rather than worrying about breaking changes.(WebSocketプロトコルは現在も活発に変更が続けられています。将来的になんらかの合意ができるまでは、後方互換性にこだわるよりも、現在の仕様に準拠するのを優先していきます)
これは将来的にも仕様が変わる可能性を示唆しています。現に00にドラフト番号が降り直された後も番号は更新され続け、最近では10月17日にドラフト03がアップデートされました。とくにここ最近の変更はデータフレーミングのフォーマットの変更が多く施されており、これらの変更がブラウザの実装に反映されると、ドラフト間の互換性が再びなくなってしまいます。
ここまで悲観的な書き方をすると「まだWebSocketなんて使わない方が良いかな」と思われるかもしれませんが、これは現在ドラフトの仕様が固まる過渡期に当たるので、もう少しの辛抱だと思います。
また、今まで「Comet」と呼ばれていたプッシュテクノロジー群の総称は、オープンなスタンダードがなかったために、各「Cometサーバ」実装者間での情報共有が難しい状況でした。WebSocketでは逆に、Comet機能も備えたWebサーバ「Jetty」開発者であるGreg Wilkinsさんらが、今までComet機能実装で培った知識をプロトコルの変更に役立てるなど、ベストプラクティスが培われているのは頼もしいことです。
Chromiumが昨年12月に実装した時は「Chromiumだけでサポートされててもね」という雰囲気でしたが、今年の春頃から各ブラウザも実装してきたようです。私のActivity Monitorの例でも使用した node-websocket-serverの作者はGithub上で対応ブラウザの一覧をメンテしているようです。現在のところChrome、Safari、Firefox、Operaで実装されているようです。肝心のIEですが、今のところサポートのアナウンスは出されていません。
しかしながらFlashでWebSocketのエミュレーションを行うweb-socket-jsというライブラリがあるので、このライブラリを組み込むことで、IEでも実質対応可能です。
モバイル環境に関してですが、各ブラウザがWebSocketを実装した後も長らくの間、Mobile SafariはWebSocketに対応していませんでした. またMobile SafariはFlashも搭載していないため、上記のエミュレーションのライブラリも使うことができません。(それにしびれを切らして、PhoneGapというモバイルフレームワークではiPhone用のWebSocketを独自実装した人もいます。しかしながら、ちょうどこの原稿を公開する直前になって「iPhoneのiOS 4.2でWebSocketがサポートされているようだ」という報告をツイッター上で見かけるようになりました(ただ正式発表は現在のところまだ出ていないようです)。
これまでWebSocketがもたらしてくれるステートフルの長所ばかりを取り上げていましたが、もちろん問題点もあります。それはスケールアウトが簡単にできない点です。
これまでのWebサーバであれば、スケールアウトさせた場合は単純にWebサーバを何台も立ち上げれば良かったはずです(その分DBにアクセスが集中するという副作用を生みがちですが)。毎回のリクエストはステートレスであるため、どのサーバに接続しに行っても同じレスポンスが得られるはずです。サーバの前にロードバランサなどを置いておけば簡単に負荷分散が可能です。
しかしながら、ステートフルなWebSocketサーバの場合はどうでしょう。一度接続が確立すると、同一のWebSocketサーバにつなぎっぱなしの状態になります。最初1000ぐらいのアイドルなコネクションがつながっていて、一気に大量のメッセージが送られて負荷が増えた際、ほかのサーバに負荷を切り替えようとすると、サーバの方で独自の仕組みが必要になってきます。
ほかにも、他の接続クライアントにメッセージをブロードキャストする際にも課題が残ります。node-websocket-serverのサンプルの一部を例に見てみましょう。
server.addListener("connection", function(conn){ log("opened connection: "+conn.id); server.send(conn.id, "Connected as: "+conn.id); conn.broadcast("<"+conn.id+"> connected"); conn.addListener("message", function(message){ log("<"+conn.id+"> "+message); conn.broadcast("<"+conn.id+"> "+message); }); });
node-websocket-serverには、現在サーバに接続しているすべてのメンバにメッセージをブロードキャストする「conn.broadcast()」という便利なファンクションがあるのですが、すべての接続情報がメモリ上に保存されているため、複数サーバ間のメンバにメッセージを送ることはできません。この場合はステートレスな既存のWebサーバと同じように、データベースや、何らかのメッセージングシステムを経由してステートをシェアする必要が出てきます。
ただし、WebSocketサーバはAjaxやCometに比べてネットワークオーバーヘッドが比較的少ないので、スケールアウトが必要になるまでのWebトラフィックというのは相当な量になると思います。
最後に考えるべき点としては、いかに既存のステートレスなアプリとステートフルなアプリを組み合わせるかということがあります。WebSocketサーバがHTTPとWebSocketの両方のプロトコルをサポートしていれば良いのですが、そうでない場合、2つのサーバを立ち上げる必要が出てきます。WebSocket自体はクロスドメイン可能なのでHTTPサーバとは別ポートで立ち上げても良いのですが、適当なポートを指定した場合、ファイアウォールなどで遮断される可能性があります。かといってサーバの前にプロキシサーバを入れる際には、WebSocketサーバの接続がプロキシに切断されないよう設定をしっかりする必要が出てきます。そういう設定が煩わしい場合は、WebSocketサーバとHTTPサーバを異なるインスタンス上で立ち上げることもできますが、それはそれで煩わしいかもしれません。
WebSocketはまだ新しいプロトコルであるため、WebSocketプロトコルを理解しないプロキシサーバを経由した場合、ものによっては接続に失敗したり、接続成功後にコネクションを切断されてしまう可能性もあります。ある程度安定した成功率を保証しようとすると、WebSocket Secure(wss)をポート443経由で接続する必要があります。
サーバサイドですが、昨年の12月にChromeブラウザが最初にWebSocketを実装した辺りから、node.js以外にも様々な言語での実装が出てきたので、主要なものをいくつか紹介しましょう。先ほどあげた考慮点にどれだけ各実装が対応しているかも、分かる範囲内で比較することにしてみます。
気になるパフォーマンスに関してですが、node.jsとem-websocketに対して5000〜2万コネクションを張り、一斉にブロードキャストした際の遅延を計る簡単なスクリプトを作って比較したところ、両者ともにほぼ同じようなパフォーマンスを示しました(横軸が接続数、縦軸が遅延時間を秒単位で示したものです)。スクリプトはgithub上にありますが、現在このスクリプトが対応しているのはドラフト75のみです。
ただ、ベンチマークの仕方やサーバの設定によって数字はいくらでも変わるので、異なるベンチマーク記事の結果だけを比較してもあまり意味がないと思います。例えば「512000 concurrent websockets with Groovy++ and Gretty」と題した記事では、8台を使ってテストしていますが、私のベンチマークスクリプトは1台のクライアントマシンを使っているだけです。
Copyright © ITmedia, Inc. All Rights Reserved.