前:NodeにLindaを実装した
linda-socket.ioはsocket.io上に実装してるので、herokuでも動く。
http://linda-job-queue-sample.herokuapp.com
ソースコード https://github.com/node-linda/linda-job-queue-sample
lindaなので、サーバー側でlindaにsocket.ioを読むだけで、サーバー側のコードまったく書く必要なくクライアント側だけでメッセージングできる。
完全にこれをnodeに移植しただけなので解説は割愛する
Lindaによるブラウザ上での分散処理の例
1月102014
0
12月312013
艦これの冬のイベントやりながらcoffee書いてたらLindaができた。おかげで三隈大和大鳳以外全部集まったけどコミケに行けなかった。
前にRubyで実装したこれ
Ruby上に並列言語拡張Lindaを実装してWebSocket/Cometで使えるようにした
を
Node+Socket.IOで実装しなおした。
https://npmjs.org/package/linda-socket.io
RubyよりNode+coffeeの方が並列処理書きやすい。特に本体よりもテスト、coffee+asyncにだいぶ助けられた。あとなんだかんだでEventMachineに気を使って書くのが疲れる。
Node版はタプルに配列が使えないようにした。全部Object(hash)にした。流れているデータを読んで意味がわかりにくいのと、request-response形式で返すタイプの時にマッチングがしにくいため。
インストール
npmでインストールできる。npm install linda-socket.io
使う
Socket.IOのインスタンスを食わせると既存のSocket.IOの通信を邪魔せずにLinda機能が増える。ふつうnodeでwebアプリ作るとしたらhttp.createServerとsocket.ioでやると思うけど、それらのインスタンスをLindaに渡せば使えるようになる。
var http = require('http');
var app_handler = function(req, res){
// your web app code
};
var app = http.createServer(app_handler);
var io = require('socket.io').listen(app);
var linda = require('linda-socket.io').Linda.listen({io: io, server: app});
app.listen(3000);
console.log("server start - http://localhost:3000");
詳しくはUsageに書いた。
12月132013
asearchという文字列が似ているかどうか判定するrubygemがある。
増井先生が作ったもので、gyazzのページ名サジェストなどに使われている。
それをnodeに移植した。
https://npmjs.org/package/asearch
https://github.com/shokai/node-asearch
特徴
- はやい
- byte列を比較しているだけ
- 結果はtrue/falseで返ってくる
- あいまい度は指定できる(0〜3まで)
- 他のライブラリに依存していない、pure javascript
インストール
npm install asearch
使い方
Asearch = require 'asearch'
a = new Asearch 'abcde'
console.log a.match 'abcde' # => true
console.log a.match 'AbCdE' # => true
console.log a.match 'abcd' # => false
console.log a.match 'abcd', 1 # => true
console.log a.match 'ab de', 1 # => true
console.log a.match 'abe', 1 # => false
console.log a.match 'abe', 2 # => true
typoを判定
a = new Asearch 'cheese burger'
console.log a.match 'cheese burger' # => true
console.log a.match 'chess burger', 2 # => true
console.log a.match 'chess', 2 # => false
2バイト文字もok
a = new Asearch '漢字文字列'
console.log a.match '漢字文字列' # => true
console.log a.match '漢字文字烈' # => false
console.log a.match '漢字文字烈', 2 # => true
このように手っ取り早く2つの文字列が似ているかどうか判定できて便利。
Nodeでビット演算
RubyからJavaScriptへ移植するにあたってビット演算がちょっと違った。右シフト >> はjavascriptでは符号なし右シフト >>> にしないと符号が反転してしまう事がある。10分ぐらい気付かなかった。
|= とか >>>= とかも使える。
coffee-scriptだとさらに0b1010のような2進数記法も使えて便利。
RubyのString#unpackはNodeにもjspackという実装があるが、2byte文字に対応していなかったので自前実装した。
12月062013
methodmissingというnpmがあって、rubyのmethodmissingと同じく存在しなメソッドを呼び出した時にno method errorにするのではなく、メソッド名と引数を横取りできる。
使用例
mm = require 'methodmissing'
class Foo
baz: "bazbaz"
foo = new Foo()
## methodmissingを適用
foo = mm foo, (func_name, args) ->
console.log "missing-method '#{func_name}' called"
console.log args
## 普通のメソッド呼び出し
console.log foo.baz
## 存在しないメソッドを呼び出す
foo.kazusuke("niku", "beer", "rice")
実行結果
bazbaz
missing-method 'kazusuke' called
{ '0': 'niku', '1': 'beer', '2': 'rice' }
存在しないメソッドが呼び出された時に、動的にメソッドが存在するフリをできるのはWebAPIのラッパーを作る時などに便利。
高速化
ベンチマークを取ってみたら、
存在しないメソッド呼び出しをキャプチャーするのは6〜7倍ぐらい速くなった。普通のメソッド呼び出しと比べて10倍遅い程度。
また、methodmissing適用した後のオブジェクトは通常のメソッド呼び出しが100倍ぐらい遅くなっていたのを、9倍遅い程度に抑えられた。
(methodmissingでメソッドを呼び出すコストが通常のメソッド呼び出しに比べて10倍ぐらい遅くなるだけで、呼び出されたメソッド全体の速度が遅くなるわけではないので、ループで何万回も呼び出す等でない限りこのコストは無視していいと思う)
といっても修正したの1行だけなんだけど、けっこうこういうので変わるものだな
存在しないプロパティを呼び出した時の動作が遅いのがまだ気になる。
12月052013
rubyではなくNodeのSocket.io上にlindaを実装しようと思っていて、そのために
- socketio clientをグループに分けるroom機能について知りたい
- socketioを拡張するnpmをどうやって実装するのがいいのか調べる
このように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