チャットのサンプルがあれば使い方わかるかな、と思ったけどまだ難しいらしいので
もっとミニマムなサンプルアプリを作ってみた。
ブラウザを2つ開いてボタン押すと動く。
dev.shokai.org:4100
ソースコード
shokai/rocketio-hello-world · GitHub
サーバーとJS、これだけで実装できるので便利じゃないですかね?
前:Sinatra RocketIOというプラグイン作った、これでWebSocketとCometが使える
クライアントのグループを作る機能を追加した。
例えばチャットルームを分ける時などに使ってください。
クライアント側が接続する時にチャンネル名を指定すると、サーバー側で管理されているチャンネルリストに追加される。
var io = new RocketIO({channel: "チャンネル名"}).connect();
io.on("hoge", function(data){
alert(data);
});
サーバー側からチャンネルを指定して送信する
Sinatra::RocketIO.push :hoge, "ホゲ", :channel => "チャンネル名"
サンプルも更新してあるのでどうぞ
こうすれば、 http://アプリ名/chat/チャットルーム名 でグループ分けできる。
sinatra側でURLからチャットルーム名を取り出してチャンネル名とし、
get '/chat/:channel' do
@channel = params[:channel]
haml :chat
end
viewテンプレートでjavascriptにチャンネル名を渡して
:javascript
var channel = "#{@channel}";
接続時にchannelを指定する。
var io = new RocketIO({channel: channel}).connect();
Sinatra RocketIOで家のRaspberry PiのArduinoをリアルタイムで見る
RocketIOのサンプルとして、それなりに激しい処理が低スペックマシンで動くかテストするために作った。
http://status.shookai.org
ソースコード
https://github.com/shokai/rocketio-arduino-sample
こういうのが表示されてるはず
物理的な意味ではこうなってる
Arduino Firmata on Rubyで、ADコンバータに温度センサと明るさセンサを付けたArduinoを読んで0.3秒毎にSinatra RocketIOでブラウザにpushしている。
5秒毎にCPUとメモリ使用量もpushしてる。
あと新規接続/切断がある度に全クライアントに接続数を通知してる。
60行ぐらいのSinatraと40行ぐらいのJavaScriptでできてる。
JavaScript側を抜粋すると、こんな感じでそれぞれイベント待ちしておいて
io.on("arduino", function(data){
$("#temp").text("気温:"+data.temp+"度");
$("#light").text("明るさ:"+data.light);
});
io.on("stat", function(data){
$("#cpu").text("CPU:"+data.cpu+"%");
$("#mem").text("メモリ:"+data.mem+"%");
});
io.on("clients", function(data){
$("#websocket").text("websocket:"+data.websocket);
$("#comet").text("comet:"+data.comet);
});
サーバー側はsinatraの中にArduinoでセンサーを読むEventMachineのタイマーをベタ書き。
io = Sinatra::RocketIOCPUとメモリは同じ要領で、psコマンドの結果をparseして送ってる。
## Arduino sensors
io.once :start do
begin
arduino = ArduinoFirmata.connect ENV['ARDUINO'], :eventmachine => true
rescue => e
STDERR.puts "#{e.class} - #{e}"
next
end
EM::add_periodic_timer 0.3 do
light = arduino.analog_read 0
$logger.debug "light : #{light}"
temp = arduino.analog_read(1).to_f*5*100/1024
$logger.debug "temperature : #{temp}"
io.push :arduino, :temp => temp, :light => light
end
end
Sinatra側で定期的に行う処理は、 Sinatra::RocketIO.once :start の中で登録すると良いです。
“start”はeventmachineとwebsocketサーバーの準備が整ったら発火するイベント。
onではなくonceなのは1回目だけ呼ばれるという意味。
あと、Arduino Firmataの最近のバージョンからeventmachine用の起動オプションを付けた。
ArduinoFirmata.connect "/dev/tty.usbデバイス名", :eventmachine => trueEM::runの中でこうやって起動するとThreadのかわりにeventmachineで動くようになった。
Sinatraの中で動かすときはこうしないと、タスク切り替えがうまくいかない。
get ‘/’とかpost ‘/message’ とかDSLで登録したものをroutesという。
Sinatra::Base#routesにHashがあって、これを直接のぞける。
logoutとかloginとか、すでにrouteとして登録されている名前でユーザ名を作れないようにするとかに使えると思う。
リクエストメソッドをkeyとしたhashの子要素に配列の配列があって、1つ目の要素がパスにマッチするかどうか判定する正規表現。
{"GET"=>[[/^\/cometio\/cometio(?:\.|%2E)js$/, [], [], #<Proc:0x007fbd5cab8d78@/Users/sho/.rvm/gems/ruby-1.9.2-p318/gems/sinatra-1.3.6/lib/sinatra/base.rb:1293>], [/^\/cometio\/io$/, [], [], #<Proc:0x007fbd5cab6d98@/Users/sho/.rvm/gems/ruby-1.9.2-p318/gems/sinatra-1.3.6/lib/sinatra/base.rb:1293>], [/^\/websocketio\/websocketio(?:\.|%2E)js$/, [], [], #<Proc:0x007fbd5a1d8598@/Users/sho/.rvm/gems/ruby-1.9.2-p318/gems/sinatra-1.3.6/lib/sinatra/base.rb:1293>]], "HEAD"=>[[/^\/cometio\/cometio(?:\.|%2E)js$/, [], [], #<Proc:0x007fbd5cab7608@/Users/sho/.rvm/gems/ruby-1.9.2-p318/gems/sinatra-1.3.6/lib/sinatra/base.rb:1293>], [/^\/cometio\/io$/, [], [], #<Proc:0x007fbd5cab6500@/Users/sho/.rvm/gems/ruby-1.9.2-p318/gems/sinatra-1.3.6/lib/sinatra/base.rb:1293>], [/^\/websocketio\/websocketio(?:\.|%2E)js$/, [], [], #<Proc:0x007fbd5a227a80@/Users/sho/.rvm/gems/ruby-1.9.2-p318/gems/sinatra-1.3.6/lib/sinatra/base.rb:1293>]], "POST"=>[[/^\/cometio\/io$/, [], [], #<Proc:0x007fbd5cab5d80@/Users/sho/.rvm/gems/ruby-1.9.2-p318/gems/sinatra-1.3.6/lib/sinatra/base.rb:1293>]]}
たとえば /cometio/cometio.js を削除するなら
Sinatra::Application.routes["GET"].delete_if{|route|
"/cometio/cometio.js" =~ route[0]
}
nodeのSocket.ioっぽい物のRuby版を作った。
https://github.com/shokai/sinatra-rocketio
依存
EventMachineが有効なWebサーバー(thinとか)と、jQueryが必要。Rubyは1.8.7〜2.0.0まで動く。
Sinatraで使う
インストール
gem install sinatra-rocketio
ブラウザとか回線に応じてWebSocketとCometの使える方が自動的に選ばれる。
切断されても定期的に再接続を試みるようになってる。
sinatraに読みこむだけで使える。sinatraのプロセス内にwebsocketサーバーも組み込まれる。
require 'sinatra'
require 'sinatra/rocketio'
modular styleではclass内でregisterすればok
class MyApp < Sinatra::Base
register Sinatra::RocketIO
## 略
end
サーバーからブラウザにpushするの簡単にできる
Sinatra側
io = Sinatra::RocketIOpushはsinatraのgetとかpostの中に書いてもいい。どこに書いてもpushできる。
io.push :temperature, 35 # 全てのクライアントに送信
io.push :light, {:value => 150}, {:to => session_id} # 特定のクライアントへ
クライアント側。JSライブラリを読み込む。
<script src="<%= rocketio_js %>"></script>hamlの場合はこう
%script{:src => rocketio_js}
そしてJavaScriptに受信イベント書く
var io = new RocketIO().connect(); // WebSocketとCometの適当な方が使われる
io.on("temperature", function(value){
console.log("温度 : " + value);
}); // => "温度 : 35"
io.on("light", function(data){
console.log("明るさ : " + data.value);
}); // => "明るさ : 150"
通信というより、クライアント側でイベント登録しておいてサーバーから呼び出せる感じ。
クライアントからサーバーへ送信
JS側
io.on("connect", function(){
io.push("chat", {name: "shokai", message: "hello"});
});
Ruby側
io.on :chat do |data, client|逆も同じ要領でできる。”chat”というイベントを登録しておいて受信してみた。
puts "#{data['name']} : #{data['message']} <#{client.session}> type:#{client.type}"
io.push :chat, data # 全クライアントに返送
end
チャットなので全クライアントに返送もする。
サーバーは、クライアントのセッションIDとtype(websocketかcometか)も取れる。
通信方法のまとめ
サーバー側
Sinatra::RocketIO.on “イベント名” でイベント登録して受信。
Sinatra::RocketIO.push “イベント名”, “データ” で送信。
クライアント側
var io = new RocketIO().connect(); で得たio(RocketIOのインスタンス)を使う。
io.on(“イベント名”, コールバック関数) でイベント登録して受信。
io.push(“イベント名”, データ) でサーバーに送信。
だいたいこんな感じで使える。
試しにチャットを作ってみた
http://rocketio-chat.herokuapp.com/
herokuなのでwebsocketが使えなくて全員cometで接続される。
http://dev.shokai.org:4000/
さくらVPSサーバーなのでwebsocket&comet両方とも使える。
ソースコードはここ https://github.com/shokai/rocketio-chat-sample
サーバー側Ruby 25行、クライアント側JS 39行でチャット作れてた。
構成
Comet部分はSinatra CometIOという独立したSinatraプラグインで実装してある。これを一番最初に作った。WebSocket部分もSinatra::WebSocketIOという、Cometと同じAPIで使えるやつで実装した。
RocketIOはこれらをまとめた。
イベント登録して発火する部分は、event_emitter.jsとevent_emitter.rbというJSとRubyそれぞれで使えるイベント管理ライブラリを作った。
性能
2年前に買ったMacMini上のVMWareで動かしてるUbuntuでWebSocketは1万5千クライアント、Cometは500クライアントぐらい同時接続して普通に動いた。
WebSocketは1万クライアント接続して、そのうち1つがサーバーにpush、サーバーが全クライアントに返送し終わるまで平均0.7秒ぐらい。
CPUが良ければもっといけると思う。
同時接続数を1万以上やるにはOSのファイルディスクリプタの設定とか必要。
https://github.com/shokai/sinatra-websocketio/wiki/C10K