‘Node.js’ カテゴリーのアーカイブ

Socket.IO:マルチプロセスのnode.jsで、Roomに入室したクライアント一覧を取得する

チャット等で、Roomに誰が入室しているのかを知る際の方法です。
Roomに入室したクライアント(socket id)一覧の取得は、socket.io 1.0以降では以下で取得できます。

io.nsps[yourNamespace].adapter.rooms[roomName]

ただしnode.jsをマルチプロセスで実行している場合、自プロセス以外のクライアント一覧は取得できないです。
そのためRedisのKVSを使い、以下のようにしました。

1.Roomに入室:
 RedisのKVSに、各プロセス毎に、各ルームに入室したクライアント(socket id)一覧をセット

redisClient = redis.createClient(redisPort, redisIp, {})
process_name = localIp + 'p' + port # プロセス名:プロセスのIPとポート番号を結合した文字列

#
# socketをroomにjoinし、且つRedisにsetする
#
join = (socket, room)->;
  socket.join room    # socketをroomにjoin
  key = room + ':' + process_name

  value = []
  if io.of('/').adapter.rooms[room]?
    for socketId of io.of('/').adapter.rooms[room]
      value.push socketId
  redisClient.set key, JSON.stringify(value) # JSONの文字列として値をセット
  
 
roomName = 'works#' + work_id
join socket, roomName

下記のような感じでRedisにセットされます。

redis 127.0.0.1:6379[1]> keys *
1) "users#558:10.0.2.15p1338"
2) "works#1016:10.0.2.15p1337"
3) "works#1016:10.0.2.15p1338"
4) "users#10:10.0.2.15p1337"
redis 127.0.0.1:6379[1]> get works#1016:10.0.2.15p1337
"[\"1ZFRYWVeyrMPaiqOAAAD\"]"
redis 127.0.0.1:6379[1]> get works#1016:10.0.2.15p1338
"[\"vuturxDpPBL2ZHuLAAAK\"]"

2.クライアント一覧を取得:
 RedisのKVSからワイルドカードを使ったkeysコマンドで該当するキーをすべて取得後、mgetコマンドで値を取得

#
# roomIDからsocket.idのリストを取得し、コールバック関数実行
#
clients = (room, cb, opts) ->
  key = room + ':*'
  redisClient.keys key, (err, replies)->
    redisClient.mget replies, (err, vals)->
      socketIds = []
      for val in vals
        socketIds = socketIds.concat JSON.parse val
      cb(socketIds, opts)


roomName = 'works#' + work_id
clients roomName, (socketIds)->
  console.log 'Roomに入室しているクライアント一覧:' + socketIds.join(',')

3.Roomから退室:
 RedisのKVSの値を変更または削除

#
# socketをroomからleaveし、且つredisにsetする
#
leave = (socket, room)->
  socket.leave room
  key = room + ':' + process_name

  value = []
  if io.of('/').adapter.rooms[room]?
    for socketId of io.of('/').adapter.rooms[room]
      value.push socketId
  if value.length > 0
    redisClient.set key, JSON.stringify(value)
  else
    redisClient.del key

roomName = 'works#' + work_id
leave socket, roomName

一応これで実現可能ですが、クライアント一覧取得するためだけにredisのKVSを使うというのは、無駄にリソースを使っている感じであまりいい方法ではないように思います。

また、以下のような感じでプロセス終了時にredisのデータを全て削除する必要があります。

for signal in ['SIGINT', 'SIGHUP', 'SIGTERM']
  process.on signal, ->
    key = '*:' + process_name
    redisClient.keys key, (err, replies)->
      async.each replies, (reply)->
        redisClient.del reply
      process.exit 1
      return

もっといい方法があれば、誰が教えて頂けたらと思います。

Facebooktwitterlinkedintumblrmail

Node.jsを使ってチャット

Node.jsを使ってSocket.ioを用いたチャットルームを作成したいと思います

せっかくなので、前回紹介したYeomanを使って作成

generator-socketioというジェネレータがあるようなのでコレを使います。

まずはジェネレータのインストール

XXXXX:chat XXXXX$ sudo npm install -g generator-socketio
Password:

実行するディレクトリをつくったらyoで実行

今回はbootstrap使用、スタイルシートはCSSを使うことにします

XXXXX:workspace XXXXX$ mkdir chat
XXXXX:workspace XXXXX$ cd chat
XXXXX:chat XXXXX$ yo socketio

     _-----_
    |       |
    |--(o)--|   .--------------------------.
   `---------´  |    Welcome to Yeoman,    |
    ( _´U`_ )   |   ladies and gentlemen!  |
    /___A___\   '__________________________'
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `

[?] What do you want to call this project? chat
[?] Write a brief description.
[?] What port number would you like to run on? 1337
chat
to run on port - 1337
[?] Do you want to use Bootstrap? Yes
true
[?] In what format would you like the Bootstrap stylesheets? css
css
   create server.js
   create views/index.ejs
   create public/js/app.js
   create Gruntfile.js
   create package.json
   create bower.json
   create .editorconfig
   create .jshintrc
   create .bowerrc
   create .gitignore

~~~

あとはgruntで実行するだけ。あーなんて楽ちん…

XXXXX:chat XXXXX$ grunt

-スクリーンショット 2014-11-24 23.15.25

あーbootstrap入れてなかった

Gruntfile.jsを確認した所、public/bowser_componentsにbootstrapファイルを

入れるといいみたいですね

XXXXX:chat XXXXX$ vi Gruntfile.js
~~~
    cssmin: {
      combine: {
        files: {
          'public/css/core.css': 'public/bower_components/bootstrap.css/css/bootstrap.css'
~~~

publicにbower_componentsディレクトリを作成し、公式サイトでDLしたbootstrapファイルを入れます

XXXXX:public XXXXX$ mkdir bower_components/
XXXXX:public XXXXX$ mkdir bower_components/bootstrap.css/

-スクリーンショット 2014-11-24 11.18.45

もう一回gruntで実行

おおーうまく行った!

-スクリーンショット 2014-11-24 23.14.16

すでに基本的なチャット機能が入っているようで、

もう一つブラウザを開いて文章のやりとりをしてみるときちんと動きます

せっかくなので、少し編集を加えてチャット部屋を用意してみることにします

TOP(localhost:1337)にアクセスしたらランダムなユーザ名と部屋IDを作成し、

各部屋(localhost:1337/id)に飛ぶイメージです

以下編集後

server.js 38行目〜

/*************************************
//
// chat app
//
**************************************/

// express magic
var express = require('express');
var app = express();
var server = require('http').createServer(app)
var io = require('socket.io').listen(server);
var device  = require('express-device');

var runningPortNumber = process.env.PORT;


app.configure(function(){
    // I need to access everything in '/public' directly
    app.use(express.static(__dirname + '/public'));

    //set the view engine
    app.set('view engine', 'ejs');
    app.set('views', __dirname +'/views');

    app.use(device.capture());
});


// logs every request
app.use(function(req, res, next){
    // output every request in the array
    console.log({method:req.method, url: req.url, device: req.device});

    // goes onto the next function in line
    next();
});

app.get("/", function(req, res){
    res.redirect('/'+ random_string(12));
});

app.get('/:room', function(req, res) {
    res.render('index', {room: req.params.room, name: random_string(5)});
});

io.sockets.on('connection', function (socket) {
    var room;    // 部屋ID
    var name;    // ユーザ名

    socket.emit('connected');

    socket.on('init', function(data) {
        room = data.room;
        name = data.name;

        socket.join(room);
        socket.broadcast.to(room).emit('blast', {msg:""+ name +" connected"});
    });

    socket.on('blast', function(data, fn){

        console.log(data);
        io.sockets.to(room).emit('blast', {msg:data.msg});

        fn();//call the client back to clear out the field
    });

    socket.on('disconnect', function() {
        socket.broadcast.to(room).emit('blast', {msg:""+ name +" disconnected"});
        socket.leave(room);
    });

});


server.listen(runningPortNumber);


/**
 * ランダムな文字列を返す
 */
function random_string(len) {
    var base = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    base = base.split('');
    var str = '';
    var count = base.length;
    for(var i=0; i

index.ejsはnameとroomを追加しただけ

views/index.ejs 52行目〜

                    

app.jsも同じくnameとroom追加

public/js/app.js 13行目〜

// shortcut for document.ready
$(function(){
    //setup some common vars
    var $blastField = $('#blast'),
        $allPostsTextArea = $('#allPosts'),
        $clearAllPosts = $('#clearAllPosts'),
        $sendBlastButton = $('#send'),
        $nameField = $('#name'),
        $roomField = $('#room');

    socket.emit('init', {name:$nameField.val(), room:$roomField.val()});

    $sendBlastButton.click(function(e){

        var blast = $blastField.val();
        var name  = $nameField.val();
        if(blast.length){
            socket.emit("blast", {msg:name +': '+ blast}, 
                function(data){
                    $blastField.val('');
                });
        }


    });

ここまで編集したらもう一回grunt実行

自動的に部屋へジャンプするようになりました

-スクリーンショット 2014-11-24 22.32.44

もう一つウィンドウを開き、同じURLにアクセスした後、

元の画面で確認すると部屋に別の人が入室したことを表示してくれています

-スクリーンショット 2014-11-24 23.05.40

もちろんチャットも出来ますし

ついでに退出を知らせる機能もあります

ウィンドウを消すと"●●● disconnected"と表示されることが確認できます

-スクリーンショット 2014-11-24 23.10.16

以上。らくちん。

Facebooktwitterlinkedintumblrmail