node.js、socket.ioでチャット。切断中に受け取れなかったデータを再接続時に取得

概要

websocketの入門者の定番の「チャット」をnode.js+socket.io(とexpressを少々)で実装。
クライアントの接続が途中で切断された場合、再接続時にメッセージを取得する処理も実装したメモ。
nod32のHTTPチェックが、websocketを定期的に切断したので・・)

・画面


・全ソース

github: https://github.com/motsat/node-chat/
←node server.js
でサーバ起動し、
ブラウザで
http://起動したサーバIP:8000
すれば動く!はず。

送受信するメッセージオブジェクト

このメッセージオブジェクトでやりとりする。
サーバから、クライアントからのどちらでも基本的に形は同じ。

{         "data"      : "こんにちは",
          "type"      : 1, // 通常の文字メッセージ=1, 取得要求=0         
          "option"    : {"color" : "red"},// メッセージの場合の色など。他は今は未使用
          "createdAt" : "2010-11-11 11:11:11"} ;        

処理の流れ


最初の接続時、サーバへのソケット接続をトリガーとしてチャットメッセージを取得。
サーバへのデータ取得時、more_id=1で1番目のメッセージから取得。



接続中にチャットを送信した場合、サーバからbroadcastして他のクライアントへ送信。
また、送信元のクライアントへも同じくチャットを送信。



一時的にソケットが切断され、再接続した場合は損失分のメッセージ取得。
n個までのメッセージを取得済みだった場合、
サーバへのデータ取得時、more_id=n+1とし、n+1番目のメッセージから取得。

ソース

[クライアント側(index.ejs)]
<html>
<head> 
<script src="http://motoki.local:8000/js/socket.io.js"></script>
<script src="http://motoki.local:8000/js/json2.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
<script>
  //{{{ messageBoad
  Message = function(id){
    var data = [];
    return {
      add    : function (msg) {data.push(msg)},
      length : function (msg) {return data.length}}
  };
  //{{{ messageBoad
  MessageBoad = function(id){
    var datas  = [],
        _id    = id,
        formatData = function (data) {return "["+data.createdAt + "] : " + data.data};
    return {
        add    : function (msg) {datas.push(msg);
                                    $('<div></div>').text(formatData(msg)).css('color', msg.option.color).appendTo(_id); },

                                  length : function () {return datas.length;}
    }
  };
  //}}}
  //{{{displayStatus
  DisplayStatus = function(id){
    var _id = id;
    return {
      connect    : function () {$(_id).text('connecting..').css('color','blue')},
      disconnect : function () {$(_id).text('disconnected').css('color','red')},
      setId      : function (id) {_id = id}}
  };
  //}}}

  var socket      = new io.Socket('motoki.local',{port:8000}), 
      dispStatus  = new DisplayStatus('div.#connect_status'),
      msgBoad     = new MessageBoad('div.#message_boad'),
      messageType = {'GET_DATA' : 0, 'STRING' : 1};

  socket.connect();
  socket.on('connect',    onConnect);
  socket.on('disconnect', onDisconnect);
  socket.on('message',    onMessage);

  $(function(){
    $('input#send_button').bind('click', sendMessage);;
    $('input#switch_socket_button').bind('click', switchSocketConnect);;
  });
  //{{{sendMessage
  function sendMessage()
  {
    var msg  = {"type"   : messageType.STRING,
                "option" : {"color" : ""},
                "data"   : $('input#send_body').val()};
      try{
          socket.send(msg);
      } catch(e) {
          alert('socket.send() error : ' + e);
      }
  }
  //}}}
  function onConnect()
  {
      log('onConnect');
      var msg = {"type"    : messageType.GET_DATA,
                 "more_id" : msgBoad.length()};
      socket.send(msg);
      dispStatus.connect();
  }
  function onDisconnect()
  {
      log('onDisconnect');
      dispStatus.disconnect();
  }
  function onMessage(msg)
  {
      msgBoad.add(msg);
  }
  function switchSocketConnect(msg)
  {
    if (socket.connected) {
      socket.disconnect();
    } else {
      socket.connect();
    }
  }
  //{{{log
  function log(msg)
  {
      if (typeof console == 'object') {
            console.log(msg);
      }
  }
  //}}}
</script>
</head>
<body>
  <div id='connect_status'>choge</div>
    message : <input id='send_body' value='' type="text"/>
    <input type="button" id='send_button' value='送信'/>
    <div><input type="button" id='switch_socket_button' value='ソケットON/Off'/></div>
    <hr/>
    <div id='message_boad'></div>
    <hr/>
</body>
</html>
サーバー側(server.js)
var io            = require('socket.io'),
    express       = require('express'),
    messages      = [],
    messageType   = {'GET_DATA' : 0, 'STRING' : 1} ;    // GET=取得要求、 STRING=チャットメッセージ


var app = express.createServer();
app.configure(function(){
  app.set('views', __dirname + '/views');
  app.use(express.static(__dirname + '/public'));
  app.set("view options", { layout: false });
  app.get('/', function(req, res) {
    res.render('index.ejs');
    });
});

app.listen(8000);

var socket = io.listen(app);
socket.on('connection', function(client){
  client.on('message',    function(msg) {onMessage(msg, client);});
  client.on('disconnect', function()    {onDisconnect(client)});
});

function addMessage(msg)
{
  msg.id        = messages.length + 1;
  msg.createdAt = getNowDate();
  if (msg.option.color == '') {
    msg.option.color = 'black';
  }
  messages.push(msg);

  return msg;
}


function onDisconnect(client)
{
  msg  = {"data"      : client. sessionId+"が切断されました",
          "type"      : messageType.STGING,
          "option"    : {"color" : "red"},
          "createdAt" : getNowDate()} ;
  socket.broadcast(msg);
}

function onMessage(msg, client)
{
  if (msg.type == messageType.GET_DATA) {
    if (typeof msg.more_id == 'number') {
      client.send(messages.slice(msg.more_id));
    }
  } else {
    addMessage(msg);
    client.send(msg);
    client.broadcast(msg);
  }
}

function getNowDate(){
  d = new Date();
  return  d.getFullYear() + "-" + (d.getMonth()+1) + "-" + d.getDate()+ " "+ d.getHours()+':' +d.getMinutes()+':' + d.getSeconds();
}
//}}}