0

Engine.IO単体でチャットを作った

友達に「Socket.IOはメモリリークヤバイから、新規開発なら迷わずEngine.IOを使え」と言われたのでEngine.IOを直接さわってみた。
Engine.IOはSocket.IOの内部で使われている部品で、「websocketでもajaxでも何でもいいのでとにかく通信ができる状態」を作るライブラリ。
もともとSinatra::RocketIOlinda-socket.ioRuby用socket.ioクライアントを作る時に中身は覗いてたけど単体で使った事はなかったし、どういう実装か忘れがちなのでメモする。

Chatを作った

とりあえずchatを作った。Herokuでwebsocketを動かしている。
サーバークライアントも50行ぐらいで書けた。



感想としては、Engine.io単体で使うと、綺麗なアプリケーションを書くにはその上に(フル機能ではないにしても)Socket.IO的な物を実装しないと無理だなと思った。やっぱり通信がしたいのではなくて、メッセージングがしたい。

なお似たような実装でSocket.IOでのチャットも以前作った。ひと通りの機能や挙動を確認するのにはチャットを作るのがよいと思う。


Engine.IOに無い機能(Socket.IOが実装してる機能)

Engine.IOはSocket.IOの後継、ではなく内部部品で、wsというwebsocket npmをラップしつつhttpからのupgrade機能とxhr-pollingやjsonpでのフォールバックを付けたライブラリ。
生のwsを直接使ってるのに近いので、バイナリのやりとりはしやすそうな気がする。

低レイヤーな分、気の利いた機能はない。そういうのはSocket.IOが担当している。

クライアントライブラリをpathに追加してくれる機能

自分でengine.io-clientからengine.io.jsを持ってきて、配置する必要がある。

Socket.IOでは/socket.io/socket.io.jsとかがサーバーのpathに現れてくれる。
ちなみにこの部分、connectのリクエストを横取りするためにEventEmitterが埋め込んでるコールバックのevents配列を直接いじっててすごい事になっている。


websocket切断時の自動再接続

websocketが切断されても、クライアントライブラリは自動再接続してくれない。on(‘close’)などのイベントで自分で接続し直す必要がある。

Socket.IOでは特に何も考えなくてもクライアントは再接続してくれる。サーバー側は60秒毎に全クライアントの生存確認を行っていて、そのためにクライアントは25秒ごとにheartbeatを送っている。

オブジェクトの自動シリアライズ・デシリアライズ

Socket.IOではオブジェクトをそのまま送りあえるけど、Engine.IOはsend(string or buffer)しかないのでJSONやmsgpackで自分でシリアライズ・デシリアライズしろという実装になっている。
JSONじゃない物が来た時とか、try catchして〜とやってると一気にコードが長くなってつらい。

リモートのイベントをdispatchする機能

emit(イベント名, データ)でリモートのイベントを発火させられない。
send(string or buffer)とon(‘message’, callback(string)) しかないので、受け取ったオブジェクトを自分でJSON.parseするなどして扱わなければならない。
EventEmitterとか使わないと巨大なswitch文が生まれるし、まあこの辺を自動的にやってくれてたのが、ものすごくコード量を減らしてくれてた事がわかる。

クライアント全員へのブロードキャスト

sockets.emitみたいなのは無いので、
for client_id, client of engine.clients
client.send JSON.stringify {type: "chat", message: "ハロー"}
のように自分でclients全員に送らなければならない

rooms機能が無い

クライアントをグループ分けするrooms機能は無い。
engine.io-roomsのようなプラグインを使う必要がある。


Engine.IOにある機能

on “packet”や”packetCreate”などの低レベルなイベントがあるので、デバッグに便利そう

0

Ruby版socket.io用クライアント 自動再接続機能付けた

node版にもともとある機能だけど、切断されたら自動的に再接続する機能をv0.0.3から付けた。
あとTravis CIでテストするようにした。

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


もともとwebsocket-client-simple gemに切断時のイベントがあるんだけどnode版と同じようにサーバーからのheartbeatのタイムアウトで判定するようにした。

切断されたらランダムに20〜40秒待ってから再接続する。


サンプルコード、rubyのとcoffee-scriptと入れておいた。ほぼ同じ感じで書けてよい。

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

socket.ioのroom機能を使ってchatを作った

rubyではなくNodeのSocket.io上にlindaを実装しようと思っていて、そのために

  • socketio clientをグループに分けるroom機能について知りたい
  • socketioを拡張するnpmをどうやって実装するのがいいのか調べる
必要があったので、chatを作った。


このようにshebangごとに部屋が別れる。

最近websocketがherokuで使えるようになったのでherokuに置いた。

ソースコード https://github.com/shokai/socketio-chat-study


実装

サーバーもクライアントも40行ぐらいでできた。express等WAFは使っていなくて、HTMLやJSをサーブする部分はhttpモジュールで書いた。
httpのインスタンス作る時にsocketioのインスタンスを食わせると、socketio.jsなどが生えるという仕様は面白い。nodeにはWSGI/Rack/PSGIのような物が必要ないように見える。


roomにクライアントを追加するには、サーバー側でsocket.join(‘部屋名’)で追加する。
クライアントが切断されてもroomから自動的に抜けてくれるわけではなく、その部屋に新しいクライアントをjoinした時にようやく切断済みクライアントがリストから取り除かれる。
なのでクライアントのdisconnectイベントでleaveさせた。

client.coffee
接続できたら、shebangから部屋名を決めてサーバーに”join_to_room”イベントを送る
socket.on 'connect', ->
room =
if (shebang = location.href.match(/#([^#]+)$/))
shebang[1]
else
'room1'
socket.emit 'join_to_room', room ## 部屋に入れてもらう

server.coffee
新規クライアントが接続してきて、”join_to_room”が来たら部屋に入れる。onceなので複数の部屋に1つのクライアントが入ることはない。
io.sockets.on 'connection', (socket) ->
socket.emit 'chat', {msg: 'hello new client!'}

## クライアントを部屋に追加する
socket.once 'join_to_room', (room) ->
console.log "<#{socket.id}> join_room to \"#{room}\""
socket.join room

## echo to room
socket.on 'chat', (data) ->
console.log "<#{socket.id}> #{data.msg}"
io.sockets.to(room).emit 'chat', data ## 部屋の全員にecho

socket.once 'disconnect', ->
socket.leave room ## 切断したら部屋から退去
notify_rooms()

notify_rooms()

## 現在存在する部屋名と人数を全クライアントに通知
notify_rooms = ->
rooms = {}
for name, ids of io.sockets.manager.rooms
## 全てのクライアントはデフォルトで "" という部屋に入っている
## "/" から始まる部屋名はshebangで指定された部屋
if name.match /^\/.+$/
name = name.replace /^\//, ''
rooms[name] = ids.length
io.sockets.emit 'rooms', rooms


Herokuでwebsocket

Using WebSockets on Heroku with Node.js | Heroku Dev Center

コマンド1発でherokuでwebsocketが使えるようになる。
heroku labs:enable websockets


Herokuへのデプロイは簡単で、

  • websocket proxyが通るように、httpとwebsocketは同じportにする
  • 環境変数PORTを読んでwebサーバーを起動する
  • 起動コマンドをProcfileに書く
  • npmの依存パッケージはpackage.json
さえ守っていればすぐHerokuに置けるというのは便利