0

Sinatra RocketIOのcometの負荷が軽くなった

sinatraにSocketIOっぽいwebsocket/comet機能を追加できるプラグインを作っている。
今日はcometの負荷を軽くした。

shokai/sinatra-rocketio · GitHub


comet、複数回のサーバーへのpushをキューに溜めてまとめて送信するようにした。
cometioのpost_intervalで設定できる

設定例

set :cometio, :timeout => 120, :post_interval => 2
set :websocketio, :port => 8080
set :rocketio, :websocket => true, :comet => true # enable WebSocket and Comet
post_interval => 2 でタメたキューを2秒ごとにサーバーに送る。


websocketは1プロセスで1万5千ぐらい接続できるんだけど、cometが重くて足ひっぱってたのでこれで軽くなったと思う。

例えばこういうコードの時に今まで10回pushされてたけど、10回分まとめてajaxするので軽い。
for(var i = 0; i < 10; i++){
io.push("chat", "hoge("+i+")");
}

sinatra-cometio v0.5.1から有効になってる機能で、sinatra-rocketioとは別のgemなので
bundle updateしてアップデートしてください

0

Sinatra RocketIO v0.2.0でchannel機能がついた

前: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();

0

sinatra/rocketio/clientとem-rocketio-client作った

Sinatra RocketIOのRubyクライアントを作った。
Thread版とEventMachine版がある。
コレを使うとサーバーにwebブラウザ以外からデータを投入したり取得したりできて便利。
ワーカーに作業を任せるのにも使えなくもないかも。


Thread版

sinatra-rocketio gemに付属してる。

Sinatra::RocketIO::Client · shokai/sinatra-rocketio Wiki


例えばhttp://rocketio-chat.herokuapp.com/のCUIクライアントを作る例

#!/usr/bin/env ruby
require 'rubygems'
require 'sinatra/rocketio/client'

io = Sinatra::RocketIO::Client.new('http://rocketio-chat.herokuapp.com').connect

io.on :connect do |session|
puts "#{io.type} connect!! (session_id:#{session})"
end

io.on :chat do |data|
puts "<#{data['name']}> #{data['message']}"
end

io.on :error do |err|
STDERR.puts err
end

io.on :disconnect do
puts "disconnected!!"
end

loop do
line = STDIN.gets.strip
next if line.empty?
io.push :chat, {:message => line, :name => name}
end
Threadなので、最後にloopで止めないとRubyが終了する。


デフォルトでwebsocketを先に試すので、最初からcometを使いたいのならtypeオプションを渡す。
io = Sinatra::RocketIO::Client.new('http://rocketio-chat.herokuapp.com', :type => :comet).connect


仕様

コンストラクタにアプリのURLを渡すと、 /rocketio/settingsというJSONを読みにいってそのアプリでcometとwebsocketが使えるか確認する(RocketIOは設定でcometのみとかできる)

settingsの読み込みに失敗すると10秒ぐらい待ってからまた見に行く。

websocketとcometが両方使えるなら、websocketから接続を試す。接続できなかったらcometを使う。

cometにはHTTParty、websocketにはwebsocket-client-simpleを使っている。
どちらも切断されても自動的に再接続しにいく。


EventMachine版


EM::RocketIO::Client · shokai/sinatra-rocketio Wiki
shokai/em-rocketio-client · GitHub

eventmachine版の方が高速に動作する。
通信にem-http-clientとem-websocket-clientを使っているが、thread版と動作は同じ。


インストール
gem install em-rocketio-client


同様にチャットクライアントを作ってみる

require 'rubygems'
require 'eventmachine'
require 'em-rocketio-client'

url = 'http://rocketio-chat.herokuapp.com'
EM::run do
io = EM::RocketIO::Client.new(url).connect

io.on :* do |event_name, data|
puts "(#{io.type}) #{event_name} - #{data.inspect}"
end

EM::defer do
loop do
line = STDIN.gets.strip
next if line.empty?
io.push :chat, {:message => line, :name => name}
end
end
end

*で全イベントを取得できる。


こっちは下の2つのgemを合体させて実装している。
shokai/em-cometio-client · GitHub
shokai/em-websocketio-client · GitHub

0

Sinatra RocketIOで家のRaspberry PiのArduinoをリアルタイムで見る

RocketIOのサンプルとして、それなりに激しい処理が低スペックマシンで動くかテストするために作った。

http://status.shookai.org

ソースコード
https://github.com/shokai/rocketio-arduino-sample


こういうのが表示されてるはず


物理的な意味ではこうなってる
R0020611


Arduino Firmata on Rubyで、ADコンバータに温度センサと明るさセンサを付けたArduinoを読んで0.3秒毎にSinatra RocketIOでブラウザにpushしている。

5秒毎にCPUとメモリ使用量もpushしてる。
あと新規接続/切断がある度に全クライアントに接続数を通知してる。


60行ぐらいのSinatra40行ぐらいの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::RocketIO

## 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
CPUとメモリは同じ要領で、psコマンドの結果をparseして送ってる。


Sinatra側で定期的に行う処理は、 Sinatra::RocketIO.once :start の中で登録すると良いです。
“start”はeventmachineとwebsocketサーバーの準備が整ったら発火するイベント。
onではなくonceなのは1回目だけ呼ばれるという意味。


あと、Arduino Firmataの最近のバージョンからeventmachine用の起動オプションを付けた。
ArduinoFirmata.connect "/dev/tty.usbデバイス名", :eventmachine => true
EM::runの中でこうやって起動するとThreadのかわりにeventmachineで動くようになった。
Sinatraの中で動かすときはこうしないと、タスク切り替えがうまくいかない。

0

RocketIOの全イベント取得方法

RocketIOではevent_emitterという(node.jsのを参考にして作った)イベント管理機構を使っている(Ruby用JavaScript用


event_emitterの全てのイベントを取得する手段がある


Ruby

user.on :* do |event_name, args|
puts event_name # 呼び出されたイベント名
p args
end


JavaScript
user.on('*', function(event_name, data){
console.log(event_name + ' was called');
console.log(data);
});


RocketIOでも、これを使っているので
Sinatra::RocketIO.on :* do |event_name, args|
puts "#{event_name} - #{args.inspect}"
end
とするとサーバー側で起こったイベント全部取れるし

var io = new RocketIO().connect();
io.on("*", function(event_name, args){
console.log(event_name);
console.log(args);
});
これでクライアント側のイベント全部取れる。


関係ないクライアントにデータ送信してるかも、とか、余計なイベント送ってるかも等の調査に便利。