0

Ruby用のsocket.ioクライアント作った

node.jsのsocket.ioのRubyクライアントライブラリ作った。

rubygems.orgに既に他に2つ実装があるものの、1つは古く、もう一つはruby2.0で動かずプロジェクトが生きてない感じだったので、車輪再発明した。

socket.ioはwebsocketとかajax pollingとかflashとかiframeとかいろいろ使うけど、rubyなのでwebsocketしか実装しないことにした。

昨日のmix-inした関数を上書き定義前にalias_methodで退避するで、event_emitterとwebsocket-client-simpleにほとんど任せてるのでそんなに面倒でもなかった。70行ぐらいで実装できた。


インストール

gem install socket.io-client-simple


ソースコード

https://github.com/shokai/ruby-socket.io-client-simple

不具合などあったら@shokaiかgithubのissueにお願いします


使い方


接続
samples/sample.rb
require 'rubygems'
require 'socket.io-client-simple'

socket = SocketIO::Client::Simple.connect 'http://localhost:3000'

socket.on :connect do ## 接続イベント
puts "connect!!!"
end

socket.on :disconnect do ## 切断イベント
puts "disconnected!!"
end

## サーバーからイベント"chat"がemitされたら、このコールバックが呼ばれる
socket.on :chat do |data|
puts "> " + data['msg']
end

puts "please input and press Enter key"
loop do
msg = STDIN.gets.strip
next if msg.empty?

## サーバーにchatイベントを送る
socket.emit :chat, {:msg => msg, :at => Time.now}
end


サーバー側の実装は普通にsocket.ioのリファレンス実装みたいな感じ。

samples/chat_server.js
// socket.io chat server

var io = require('socket.io').listen(3000);

io.sockets.on('connection', function(socket){
socket.emit('chat', {msg: 'hello world (from server)'});
socket.on('chat', function(data){
console.log(data);
socket.emit('chat', data); // echo
});
});


まだ切断したら自動再接続する機能ないけど、すぐ実装する予定。

0

Macにnginx入れてwebsocket proxyした

これが参考になった
nginxを1.3.14にしたらWebSocketがProxyできた
config-com.favorymous.www.conf


おかげで家のMacでRocketIOのcomet/websocketチャットが動いた
http://chat.shookai.org/
サブドメインで判別してアプリにproxyしている。

経緯

ほぼ同時に研究室のサーバーが壊れたり、家のファイルサーバーが壊れたりしたのでMacとか色々環境を再構築することになった。

昔研究室のマシンをセットアップした時はみんなRubyしか使ってなかったので、Apache+Passengerにしておいたけど
最近はNodeとかWebSocketとかアプリプロセス内に小さなworker持ったりとかするようになって、Apache+Passengerでは無理になったので

nginxを前に置いて、後ろのアプリ(たくさん、ユーザー権限で動く)にproxyする事にした。
(アプリはforemanなどでlaunchdにインストールする)


インストール

brew info nginx
brew install nginx --devel
develなら、変なことしなくてもwebsocket proxyが使える1.3.14がインストールできる。


起動
nginx
http://localhost:8080 で起動している事を確認。
root権限でないと80番では起動できない。

終了
nginx -s stop

launchd 起動ファイルをコピー
サービスとしてlaunchdにインストール
cp /usr/local/Cellar/nginx/1.3.14/homebrew.mxcl.nginx.plist ~/Library/LaunchAgents/
launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist

アンインストール
launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist
brew uninstall nginx


設定する

設定ファイルはここにある
/usr/local/etc/nginx/nginx.conf

編集して、有効にするにはnginxを再起動しなければならないが
設定のシンタックスだけチェックもできる。エラーメッセージもわかりやすい。
nginx -t


nginxからwebsocket使ってるアプリにproxy


まずSinatra RocketIOで作ったwebsocketアプリでチャットするアプリを、HTTP port 5000 / websocket port 33100で動かしてある。
これ shokai/rocketio-chat-sample · GitHub


nginx.confのhttp{ }の中にupstreamとserverを書く
chat.shokai.org へのアクセスを http://127.0.0.1:5000 に流す。
  upstream rocketio-chat {
server 127.0.0.1:5000;
}

server {
listen 8080;
charset utf-8;
server_name chat.shookai.org;
root /Users/sho/projects/rocketio/rocketio-chat-sample/public;
location / {
try_files $uri $uri/index.html $uri.html @rocketio-chat;
}
location @rocketio-chat{
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Document-Root $document_root;
proxy_set_header X-Document-URI $document_uri;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_pass http://rocketio-chat;
}
}

nginx再起動すると、cometもwebsocketも通っていた。


設定はgithubで管理することにした
shokai/shookai.org.nginx.conf · GitHub


最終的に、 /usr/local/etc/nginx/apps/アプリ名.conf を作ればどんどん増やせるようにした。
これをテンプレとする。
upstream rocketio-chat {
server 127.0.0.1:5000;
}

server {
listen 8080;
charset utf-8;
server_name chat.shookai.org;
root /Users/sho/projects/rocketio/rocketio-chat-sample/public;
location / {
try_files $uri $uri/index.html $uri.html @rocketio-chat;
}
location @rocketio-chat{
include includes/proxy.conf;
proxy_pass http://rocketio-chat;
}
}

0

websocket-client-simple 作った

eventmachineに依存していないruby用websocketクライアントでまともなのが無いので作った。

shokai/websocket-client-simple · GitHub

既にwebsocket-clientというgemがあるけど、ドラフト0しか実装されていないしpull requestできるリポジトリが無かったので作った。
名前はwebsocket-client2というnode臭がするのと後ろにliteとかsimpleとか付けるperl臭がするのと、わけわからない名前付けるruby臭で迷った。


インストール


gem install websocket-client-simple


使い方


JavaScriptのWebSocketと同じように使える
require 'rubygems'
require 'websocket-client-simple'

ws = WebSocket::Client::Simple.connect 'http://example.com:8888'

ws.on :message do |msg|
puts msg.data
end

ws.on :open do
ws.send 'hello!!!'
end

ws.on :close do |e|
p e
exit 1
end

loop do
ws.send STDIN.gets.strip
end
threadで動くので、最後にloopしておかないとRubyが終了してしまいます


サンプルに、
  • rubyで書いたwebsocketチャットサーバー
  • webブラウザ用チャットクライアント
  • websocket-client-simpleで書いたチャットクライアント
を入れておいた。



websocketのデータのparseにはimanel氏のwebsocketというgemを使った。
とても良くテストなども書かれていてメンテされているので、TCPSocketをそれに食わせるだけでクライアント書けた。

あとちゃんと試してないけど多分
ws.send binary_data, :type => :binary
でバイナリ送信できると思う。

0

em-websocketで1万クライアント以上さばく方法

em-websocketの接続数の上限が1015ぐらいなんだけど増やせた。forkとかは使わない。

環境はUbuntu12.04。
Macはepoll使えないので無理。

まずこちらを参考にファイルディスクリプタの上限を増やしておく。
ファイルディスクリプタの上限値を増やす – そ、そんなことないんだから!


で、EM::runの前にepollを使うようにしてset_descriptor_table_sizeを設定すればおk

require 'eventmachine'
require 'em-websocket'

EM.epoll
EM.set_descriptor_table_size 60000
EM::run
EM::WebSocket.run :host => "0.0.0.0", :port => 8080 do |ws|
## 略
end
end

クライアント側はem-websocket-clientを使って試した。こちらもEventMachine起動前にEM.epollしてEM.set_descriptor_table_sizeしておく必要ある。

1万クライアント接続している状態で、全クライアントに数byteのデータ送るのに0.5秒ぐらいかかった。
14000ぐらいでなんか動きが怪しくなった。

参考:File: EPOLL [EventMachine]


なかなか上のページに辿りつかなかったんだけど、thinの中でem-websocketを使ってる時にthinの–max-connsオプションを増やしたらなぜかwebsocketの方も接続数が増えちゃったので、thinとrackのソース読んで追って行ったらたどり着いた。

0

HTML+JavaScriptをArduinoに直結できるシリアルポートサーバーを作った

SerialPort Serverを使うと、HTMLとJSをArduinoに直結できる。JavaScriptを少し書くだけでArduinoに「カーテン開けろ」とか「部屋の明るさよこせ」とか命令を送れるわけだ。
Web系の技術とハードウェアの技術を同時に使うには、間に「つなぎ」が必要なので、必要な機能を全部入れたサーバーを作ってみた感じです。
(github pagesでプロジェクトページ作ってみたんだけど楽でいいですね)


こういうこと。


シリアルポートサーバーはrubygemsでインストールできる。

gem install serialport-server
which serialport-server
serialport-server --help
serialport-server /dev/tty.デバイス名
–helpでヘルプが出る。デバイス名を引数にして起動できる。
Macならsudo gem installで一発でインストールできるはず。

起動するとHTTPサーバー(http://localhost:8783)、WebSocketサーバー(ws://localhost:8784)、TCP Socketサーバー(localhost:8785)の3つが同時に起動する。

HTTPのサーバーはajaxのクロスドメイン制限を超えてデータのやりとりができるようにしてある。シリアルポートサーバーを動かして、HTMLとJavaScriptを書けばArduinoとJavaScriptが直接通信できるというわけだ。
(response headerにAccess-Control-Allow-Originを付ければ良いとyuisekiのはてブを見ていて知ったのでやってみた。)


■デモ
SerialPort Serverのページにも英語で書いたが、簡単なサンプルプロジェクトを日本語でも解説しておく。


1. Arduinoにプログラムを書き込む

2. ArduinoのDigital13番ピンにLEDを、Analog0番ピンにCdSと10kΩの抵抗を接続する
SerialPort Server sample
SerialPort Server sample

3. SerialPort Serverを起動する

4. Ajax版サンプルを開いて試してみる

5. WebSocket版サンプルを開いて試してみる


WebブラウザからLEDを点灯消灯したり、CdSの明るさの値がリアルタイムにスライダーに反映されているはずだ。
実際にAjaxサンプルのJavaScriptのコードを見てみると簡単さがわかると思う。17行でLEDとCdSを制御できている。



もちろんWebサーバーなので、複数のWebブラウザがSerialPort Serverから同時にデータを読み出そうとしても大丈夫。
増井研で1年以上、遠隔操縦ロボットの制御に使っていたプログラムを元に使っているのでプロセスを起動してたぶん3ヶ月ぐらい放置していても動き続けるぐらいに安定していると思う。