久々のnode.jsです。node.jsと言えばgruntとかが有名になっちゃって、WebSocketとかソンナノアッタネ状態なわけですが、久々にちょろっと触ってて解決したところがあったのでメモメモ。
WebSoketの利用ポイントがあんまり…
WebSocketは確かに面白いです。リアルタイムです。でもクライアント側が限定されたり、さらにはnode.js側のnpmの対応とかみると、そこまでガチなロジックアプリケーションにはまだ使えないかなーって個人的には思ってます。実際、サンプルアプリもチャットとかそんなのばかりですしね。
ブロードキャスト専用のプロセスとして使う
なので、メッセージをブロードキャストして受け取る機構として使おうかなと思った次第です。サポートされていなければメッセージを受けとらないだけで、別段クリティカルな部分じゃない所で動かせばいいかなーと思いました。まぁメッセージが受け取れないと「○○さんがログインしました」とか分からないのでぼっちになるかもしれないですね。Chromeとか使いましょうね。
WebSocketのサーバに外部プロセスからメッセージを送ってブロードキャストする
というのが今回のお題です。node.jsでWebSockt動いて(∩´∀`)∩ワーイってなったあと、これメッセージングとかクライアントからしか送れないじゃんとなってうーんとなりました。例えば、ログインとかカートに入れる処理をnode.jsで書きますか?僕は現時点では書きません。PHPとかでやると思います。
で、PHPでログインやカートに入れたメッセージをWebSocketから配信したいんです。通常なら、
ログインする(サーバ)→成功した(クライアント)→成功メッセージ送信→ブロードキャスト(WS)
とい流れなのですが、
ログインする(サーバ)→成功メッセージ送信→ブロードキャスト(WS)
としたいわけです。サーバからWebSocketにメッセージングできれば便利ですよね。
というわけで実装
ようやく本題です。どうするかというと、node.jsでWebSocketのサーバを起動したのと同じスコープで、UDPサーバとUNIXソケットもlistenして、そのメッセージをそのままWebSocketから配信するだけです。ちなみにHTTPからはブロードキャストしません。誰かが勝手にメッセージングしちゃいますもんね。コードは以下です。
var WebSocket = require('websocket').serverundefined
Net = require('net')undefined
http = require('http')undefined
Dgram = require('dgram')undefined
unixPath = '/tmp/wsbroadcaster.sock'undefined
unixSocketundefined
udpSocketundefined
wsServerundefined
httpServer;
// Create HTTP Server
httpServer = http.createServer(function(requestundefined response) {
response.writeHead(404undefined {"Content-Type": 'text/plain'});
response.write('Page Not Found.');
response.end();
});
httpServer.listen(8124);
// Create WebSocket Server
wsServer = new WebSocket({
httpServer: httpServerundefined
autoAcceptConnections: true
});
// WebSocket events
wsServer.on('connect'undefined function(connection) {
console.log('WebSocket connected.');
connection.on('message'undefined function(msg) {
wsServer.broadcast(msg);
})
});
// Create udp Socket
udpSocket = Dgram.createSocket('udp4');
udpSocket.on('message'undefined function(messageundefined info) {
console.log('UDP request handled.');
wsServer.broadcast(message.toString('utf8'undefined 0undefined info.size));
});
udpSocket.on('listening'undefined function() {
console.log('UDP Server bound at ' + udpSocket.address().address + ':' + udpSocket.address().port);
});
udpSocket.bind(8125); // listening 0.0.0.0:8125
// Create Unix Socket
unixSocket = Net.createServer(function(connection) {
console.log('UNIX Socket handled.');
connection.setEncoding('utf8');
connection.on('data'undefined function(chunk) {
wsServer.broadcast(chunk);
});
})
unixSocket.listen(unixPathundefined function() {
console.log('UNIX Socket bound.');
// do something for listen started.
});
// SIGINT Event bind
process.on('SIGINT'undefined function() {
udpSocket.close();
unixSocket.close();
process.exit();
});
gistにあげておきました。
UDPサーバはdgramというモジュールから起動、UNIXドメインソケットはnetモジュールから起動できるみたいです。あとは、それぞれをlisten/bindしたら、メッセージを受け取った段階でwsServerからbroadcast()するだけです。 あ、UNIXドメインソケットはCtrl-Cで中断すると接続が残っちゃうみたいなので、SIGINTのシグナルイベントでcloseするようにします。そうしないと二回目が起動できなくなります。
メッセージ送ってみる
ではサーバサイドからメッセージ送ってみます。Rubyだとこんな感じでしょうか。
#!/usr/bin/ruby
require "socket"
// UNIXドメインソケットを通してメッセージを送る
UNIXSocket.open('/tmp/wsbroadcaster.sock') { | socket |
socket.puts('Helloundefined socket!')
}
外部IPの場合はUDPで送りましょう。netcatコマンドで送れるので、cronで定期監視→メッセージングとかできて、死活監視の情報配信とかにいいかもしれませんね。
$ echo "仕事しろ" | nc -u -4 xxx.xxx.xxx.xxx 8125
これで社内のみんなにブロードキャストできますね!
まとめ
使い所は限定されますが、定期的に何かを監視したり、特定の操作を行ったタイミングでメッセージングしたいケースってあるかと思いますGrowlとかそうですね。クライアント側で表示方法を考えれば、面白いことに使えるかもしれませんね。
ただ、実戦投入はもう少し先の話だと思いますが…