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に置けるというのは便利

0

Node用rocketio-clientを作ったのと、npmの作り方

Sinatra::RocketIOのNode.js用クライアントを作った、という事を書くのを忘れていた。
自作Nodeモジュールをnpmjs.orgへ登録する方法もまとめる。


https://github.com/shokai/node-rocketio-client
https://npmjs.org/package/rocketio-client


感想

npmに初めて登録したけど、rubygems.orgの良い所はそのままにより洗練されてる感じがした。
あとcoffee-script、昔触った時は何これすっげえキモイと思って無理だったんだけど、その頃よりも構文が充実してたし、
Scalaを少し勉強したお陰で目が慣れてて普通に書けるようになってた。

Rubyとscala書ける人はcoffee-scriptすんなり入れると思う。


インストール

npm install rocketio-client

使う


ごく普通にSinatra::RocketIOに接続できる。websocketとcometの使える方が選択される。
RocketIO = require 'rocketio-client'
io = new RocketIO('http://localhost:5000').connect()

io.on 'connect', (io)->
console.log "connect!! (#{io.type})"
io.push 'hello', 'hello world'

io.on 'echo', (data)->
console.log "echo> #{data}"


サーバー側はこんな感じ
main.rb
io = Sinatra::RocketIO

## receive "hello" from client
io.on :hello do |message, client|
puts "> receive '#{message}' from #{client.session} (#{client.type} #{client.address})"

## push "echo" to client
io.push :echo, message
end


自作nodeモジュールをnpmjs.orgに登録する方法

package.jsonを作る

% npm init
対話形式でライブラリの名前やauthor、ライセンス形態を質問されるので答えるとpackage.jsonが生成される

package.jsonを直接編集して、必要あれば項目を追加する。
  • “dependencies” に実行時の依存ライブラリを書く
  • “devDependencies” に開発時に必要なライブラリを書く
  • 実行可能コマンドも配布する場合、 “bin” にpathを書く
  • “main” で指定されたファイルが、require ‘パッケージ名’ された時に読み込まれるファイルになる

もう一度npm initすれば対話形式で再編集できる。


ディレクトリ構成

.
├── History.txt
├── Makefile
├── README.md
├── lib
│   └── rocketio-client.js
├── node_modules

│  (node_modules内は多すぎなので省略)

├── sample
│   └── sample.coffee
├── server
│   ├── Gemfile
│   ├── Gemfile.lock
│   ├── Procfile
│   ├── README.md
│   ├── config.ru
│   ├── lib
│   ├── main.rb
│   ├── npm-debug.log
│   ├── public
│   │   ├── index.js
│   │   └── jquery.min.js
│   └── views
│   └── index.erb
└── src
   └── rocketio-client.coffee


プログラムを書く

他の人のnpmを見ていると、大抵libディレクトリの下にjsを置くようだ。

coffee-scriptを使ってる場合は、lib以外のディレクトリ(src等)を作ってcoffeeはそこに置いて、
都度jsにコンパイルしてlibに書き出す。
Makefileでやった。

サンプルコードを書く

requireはモジュール名やファイル名ではなくディレクトリ名を指定した場合に、そこにpackage.jsonがあれば”main”の項目に指定されたファイルを読み込んでくれる。

なのでsample/sample.coffeeからrocketio-clientをrequireするには
RocketIO = require '../'
と相対パスで書けばいい。


README.mdを書く

markdownでREADME.mdを書くとnpmjs.orgで綺麗に表示してくれる


publish

% npm publish
npmの開発者アカウントを作っていない場合は「adduserしろ」等の指示が表示される。言われたとおりにやる。

% npm adduser
指示に従って入力していけばok

これでもう公開されているので、誰でもインストールしてすぐ使える。


開発中のnpmを公開前にローカルで試す方法

package.jsonのdependenciesにはgitリポジトリを指定できるので、それでインストールできる。

もしくは普通nodeで開発している時はプロジェクトルートにnode_modulesというディレクトリができてその中にnpmがインストールされるので、シンボリックリンクでローカルの開発中のnpmに差し替えるという手もあると思う。