All Articles

nodejsのWebSocket-Serverに色んな方法で外部からブロードキャストする方法

久々の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').server,
    Net       = require('net'),
    http      = require('http'),
    Dgram     = require('dgram'),
    unixPath  = '/tmp/wsbroadcaster.sock',
    unixSocket,
    udpSocket,
    wsServer,
    httpServer;

// Create HTTP Server
httpServer = http.createServer(function(request, response) {
  response.writeHead(404, {"Content-Type": 'text/plain'});
  response.write('Page Not Found.');
  response.end();
});
httpServer.listen(8124);

// Create WebSocket Server
wsServer = new WebSocket({
  httpServer:            httpServer,
  autoAcceptConnections: true
});

// WebSocket events
wsServer.on('connect', function(connection) {
  console.log('WebSocket connected.');
  connection.on('message', function(msg) {
  	wsServer.broadcast(msg);
  })
});

// Create udp Socket
udpSocket = Dgram.createSocket('udp4');

udpSocket.on('message', function(message, info) {
  console.log('UDP request handled.');
  wsServer.broadcast(message.toString('utf8', 0, info.size));
});

udpSocket.on('listening', 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', function(chunk) {
    wsServer.broadcast(chunk);
  });
})
unixSocket.listen(unixPath, function() {
  console.log('UNIX Socket bound.');
  // do something for listen started.
});

// SIGINT Event bind
process.on('SIGINT', function() {
  udpSocket.close();
  unixSocket.close();
  process.exit();
});

gistにあげておきました。

https://gist.github.com/4317759

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('Hello, socket!')
}

外部IPの場合はUDPで送りましょう。netcatコマンドで送れるので、cronで定期監視→メッセージングとかできて、死活監視の情報配信とかにいいかもしれませんね。

$ echo "仕事しろ" | nc -u -4 xxx.xxx.xxx.xxx 8125

これで社内のみんなにブロードキャストできますね!

まとめ

使い所は限定されますが、定期的に何かを監視したり、特定の操作を行ったタイミングでメッセージングしたいケースってあるかと思いますGrowlとかそうですね。クライアント側で表示方法を考えれば、面白いことに使えるかもしれませんね。

ただ、実戦投入はもう少し先の話だと思いますが…