2010年11月19日金曜日

WebSocketを試す

WebSocket

最近、WebサーバからWebクライアントにデータをPushする技術としてWebSocketが脚光を浴びています。従来だと、Comet(AJAXの返答を引き延ばす技法=ロングポーリング)を用いて擬似的にPushしていたのですが、(サーバサイドもクライアントサイドも)プログラムが面倒だったり、クロスドメインへの対応がイマイチだったり、リアルタイム性が乏しかったりなどの理由で、だんだんとWebSocketに移ってきています。気づくと、サーバ側ではpywebsocketやnode.websocket.js、Jetty、mojoなどの実装が出てきましたし、クライアント側ではChromeやFirefoxが対応しており、そろそろ手を出してみても良い頃合いになってきました。ruby好きなので、サーバ側にrev-websocketを使ってみたいと思います。

rev-websocketの準備

今回は、OSにDebian Squeezeを使用します。正直、aptの便利さと、Debianのパッケージの多彩さとGUIいらずで軽量なヴァーチャルマシンを多数用意できるポータビリティにメロメロです。

まずはruby関連でインストールするパッケージです。入れたパッケージのメモを取り忘れたので、dpkg -l | grep rubyした結果を示します。不要なモノもありますが、リストに載せておきます。
libruby1.8
libsqlite3-ruby1.8
ruby
ruby-dev
ruby1.8
ruby1.8-dev
rubygems1.8

次に、rubygem関連でインストールするパッケージです。確かこれだけで良かったと思います。
rev
rev-websocket

revを検索したところ「Rev is a high performance event library for Ruby built on top of libev.」「libevのrubyバインディング」「Rubyの高速なイベント駆動IOライブラリ」などと書かれていました。便利そうなので使います。

revのサンプルプログラムを読む

まずは、revのサンプルコードを試してみます。/usr/lib/ruby/gems/1.8/gems/rev-0.3.2/README.textileを覗いてみると、echo serverの例が載ってます。gemを使ってインストールしたので、そのままではrevが無いと言われ、1行目にrubygemsのrequireを追加してあります。
require 'rubygems'
require 'rev'
HOST = 'localhost'
PORT = 4321

class EchoServerConnection < Rev::TCPSocket
  def on_connect
    puts "#{remote_addr}:#{remote_port} connected"
  end

  def on_close
    puts "#{remote_addr}:#{remote_port} disconnected"
  end

  def on_read(data)
    write data
  end
end

server = Rev::TCPServer.new(HOST, PORT, EchoServerConnection)
server.attach(Rev::Loop.default)

puts "Echo server listening on #{HOST}:#{PORT}"
Rev::Loop.default.run
このソースを見ると、(1)Rev::TCPSocketを継承したクラスを作り、(2)接続されたり、接続が切れたりしたら「on_connect」「on_close」が呼び出され、データを受信したら「on_read」が呼び出され、(3)20行目にあるようにRev::TCPServerのオブジェクトを作る際にポート番号や作成したクラスを登録すれば良いことなどが分かります。あと細かいことでは、wirteメソッドを使うとクライアントにデータを送れることも分かります。 実際に「telnet localhost 4321」で接続してみると、確かに入力した文字列がそのまま返されることが分かります。また、最終行の「Rev::Loop.default.run」によりイベントループが回り出すのですが、プログラムの実行してはこれより下には行きません。もっと複雑なプログラムを組もうとしたら、スレッド化が必要かも知れません。

WebSocketのサンプルプログラムを読む

revのソースコードの書き方が分かったところで、WebSocketのサンプルも試してみます。こちらは/usr/lib/ruby/gems/1.8/gems/rev-websocket-0.1.3/README.rdocの中にサンプルがあります。
require 'rubygems'
require 'rev/websocket'

class MyConnection < Rev::WebSocket
  def on_open
    puts "WebSocket opened"
    send_message("Hello, world!")
  end

  def on_message(data)
    puts "WebSocket data received: '#{data}'"
    send_message("echo: #{data}")
  end

  def on_close
    puts "WebSocket closed"
  end
end

host = '0.0.0.0'
port = '8080'

server = Rev::WebSocketServer.new(host, port, MyConnection)
server.attach(Rev::Loop.default)

Rev::Loop.default.run
echoサーバのプログラムと比べても、ほとんど違いがありません。構造的に違うのは、2行目のrequireと、4行目で作成しているクラスのスーパークラスがRev::WebSocketであることと、23行目でRev::WebSocketServer.newとしている箇所です。 サーバができたので試してみましょう。ただし、telnetコマンドではWebSocketサーバと通信できません。いや、正確には可能なのですが、繋がった後に渡すパラメータが複雑なので現実的ではありません。そこで、とりあえずChromeのJavaScriptコンソールから接続してみたいと思います。下記のプログラムを入力してみて下さい。接続先ホスト名は必要に応じて変更して下さい。うまく動作すれば、3行目で送信したメッセージが、アラートウィンドウに表示されます。
var ws = = new WebSocket( 'ws://localhost:8080/' );
ws.onmessage = function(event){ alert( event.data) ; }
ws.send( "This is a test message." );

重要な部分は、WebSocketのオブジェクトを生成すること、データを受信した際にonmessageが実行されること、受信したデータはevent.dataで取得できること、データ送信はsendメソッドであることです。

このように、WebSocketを使うことは難しくありません。皆さんもお試し下さい。 このエントリーをはてなブックマークに追加