node.jsのsocket.ioでチャット

nodejsでwebsocket

nodejsのsocket.ioでWebSocketができたのでメモ
仕様
 node.jsのsocket.ioで簡易なチャットを作る。
 チャットルームに同時に入れるのは2人までとする。


事前準備
・npmでsocket.ioをインストールする
npm install socket.io

○コーディング

var server = require("http").createServer(function(req, res){
  res.writeHead(200, {"Content-Type":"text/html"});
  var output = fs.readFileSync("./socket_test.html", "utf-8");
  res.end(output);
}).listen(8888);

requireでhttpオブジェクトをロードし、createServerでserverオブジェクトを作成している。serverは8888ポートでlistenにしておき、リクエストを受けたらfs.readFileSyncで指定したファイルを返している。

var io = require("socket.io").listen(server);

socket.ioをロードし、サーバーにsocket.ioの機能を追加している。

io.sockets.on("connection",  function(socket) {
  console.log("connection connected socket-id" + socket.id);
  socket.join(socket.id);

クライアント側からのsocket接続リクエストを受け取っている。socket.idによりuserの管理を行うことができる。

クライアント側でsocket接続のリクエストを飛ばしているのは以下になる。

<script src="/socket.io/socket.io.js"></script>
  <script type="text/javascript">
    var socketio = io.connect('http://nodejs_no_server:8888');
  socket.on("roomSelect", function(data){
    console.log("roomSelect");
    console.log("roomClient" + io.sockets.manager);
    var name = data.name;
    var room = data.room;
    console.log("name:" + name + "; room" + room);
    if(room.match(/[^0-9]+/)){
      io.sockets.to(socket.id).emit("result", {result:0, msg:"フォーマットエラー"});
      console.log("フォーマットエラー");
      return;
    }
    if(roomStateHash[room] == undefined) roomStateHash[room] = 0;
    console.log("room" + room +" state:" + roomStateHash[room]);
    if(roomStateHash[room]>=2){
      io.sockets.to(socket.id).emit("result", {result:0, msg:"満室です"});
      return;
    }else{
      userHash[socket.id] = name;
      userRoomHash[socket.id] = room;
      if(roomStateHash[room] == null || roomStateHash[room] == 0){
        roomStateHash[room] = 1;
      }else{
        roomStateHash[room] += 1;
      }
     var html = fs.readFileSync("./socket_game.html", "utf-8");
     io.sockets.to(socket.id).emit("result", {result:1,
                                msg:"あなたは room" + room + "に入室しました。",
                                html:html,
                                });
    }
    socket.join(userRoomHash[socket.id]);
    io.sockets.to(userRoomHash[socket.id]).emit("publish",
                                  {value:userHash[socket.id] + "がroom" + userRoomHash[socket.id] + "に入室しました"});
  });

socket.on("roomSelect", function(data)ではクライアント側からのroomSelectのリクエストを
受け取っている。

クライアントサイドでroomSelectリクエストを飛ばしているのは以下になる。

function selectRoom(){
      console.log("select Room");
      var data = {};
      var name = document.getElementById("name_input");
      var room = document.getElementById("room_input");
      data.name = name.value;
      data.room = room.value;
      socketio.emit("roomSelect", data);
    }
}

socketio.emit("roomSelect", data);で部屋の名前と自分の名前をリクエストのデータに渡している。

チャットルームの管理には以下のグローバル変数を使っている。
var userHash = {};
var userRoomHash = {};
var roomStateHash = {};
userHashにはsocket.idを引数にしてユーザー名を入れている。var userRoomHashにはsocket.idを引数にユーザーのチャットルーをを入れている。
roomStateHashにはチャットルームの人数が入るようにしている。
同室内のコメントのみをemitするためにはサーバーサイドで以下の処理を行う。
socket.join(userRoomHash[socket.id]);
io.sockets.to(userRoomHash[socket.id]).emit("publish",
{value:userHash[socket.id] + "がroom" + userRoomHash[socket.id] + "に入室しました"});
部屋番号を指定joinした上でそこにemitしている。

クライアントサイドでemitを受け取る処理は以下になる。

socketio.on("publish", function(data){addMessage(data.value);});

これで、サーバーサイドで"publish"指定のemitを受け取ったらクライアントサイドではaddMessageメソッドを実行するようになる。

コード全体は以下になる。
サーバーサイド

var fs = require("fs");
var server = require("http").createServer(function(req, res){
  res.writeHead(200, {"Content-Type":"text/html"});
  var output = fs.readFileSync("./socket_test.html", "utf-8");
  res.end(output);
}).listen(8888);
console.log("server running port 8888");
var io = require("socket.io").listen(server);

var userHash = {};
var userRoomHash = {};
var roomStateHash = {};

io.sockets.on("connection",  function(socket) {
  console.log("connection connected socket-id" + socket.id);
  socket.join(socket.id);
  socket.on("roomSelect", function(data){
    console.log("roomSelect");
    console.log("roomClient" + io.sockets.manager);
    var name = data.name;
    var room = data.room;
    console.log("name:" + name + "; room" + room);
    if(room.match(/[^0-9]+/)){
      io.sockets.to(socket.id).emit("result", {result:0, msg:"フォーマットエラー"});
      console.log("フォーマットエラー");
      return;
    }
    if(roomStateHash[room] == undefined) roomStateHash[room] = 0;
    console.log("room" + room +" state:" + roomStateHash[room]);
    if(roomStateHash[room]>=2){
      io.sockets.to(socket.id).emit("result", {result:0, msg:"満室です"});
      return;
    }else{
      userHash[socket.id] = name;
      userRoomHash[socket.id] = room;
      if(roomStateHash[room] == null || roomStateHash[room] == 0){
        roomStateHash[room] = 1;
      }else{
        roomStateHash[room] += 1;
      }
     var html = fs.readFileSync("./socket_game.html", "utf-8");
     io.sockets.to(socket.id).emit("result", {result:1,
                                msg:"あなたは room" + room + "に入室しました。",
                                html:html,
                                });
    }
    socket.join(userRoomHash[socket.id]);
    io.sockets.to(userRoomHash[socket.id]).emit("publish",
                                  {value:userHash[socket.id] + "がroom" + userRoomHash[socket.id] + "に入室しました"});
  });

  socket.on("connected", function(name){
    console.log("connected");
    var msg = name + "が入室しました";
    userHash[socket.id] = name;
    io.sockets.emit("publish", {value: msg});
  });

  socket.on("publish", function(msg){
    console.log("publish msg:" + msg);
    io.sockets.to(userRoomHash[socket.id]).emit("publish", {value:userHash[socket.id] + ":" +  msg});
  });

  socket.on("disconnect", function(){
    console.log("disconnect");
    if(userHash[socket.id]){
      var msg = userHash[socket.id] + "が退出しました";
      io.sockets.to(userRoomHash[socket.id]).emit("publish", {value:msg});
      if(roomStateHash[userRoomHash[socket.id]] != undefined){
        roomStateHash[userRoomHash[socket.id]] -= 1;
      }
      console.log("room" + userRoomHash[socket.id] + "state:" + roomStateHash[userRoomHash[socket.id]]);
      delete userRoomHash[socket.id];
      delete userHash[socket.id];
    }
  });
});

クライアントサイド

<body>
  <div id="initArea">
    <h1>名前入力</h1>
    <input type="text" id="name_input" style="width:200px;" />
    <h1>部屋選択</h1>
    <input type="text" id="room_input" style="width:30px;" />
    <button onclick="selectRoom();">選択</button>
  </div>
  <div id="gameArea" style="display:none">
  </div>
  <div id="chatArea" style="display:none">
    <button onclick="publishMessage();">語る</button>
    <input type="text" id="msg_input" style="width:200px;" />
    <div id="msg"></div>
  </div>
  <script src="/socket.io/socket.io.js"></script>
  <script type="text/javascript">
    var socketio = io.connect('http://www16436ui.sakura.ne.jp:8888');

    socketio.on("result", function(data){result(data);});
    socketio.on("connected", function(name){});
    socketio.on("publish", function(data){addMessage(data.value);});
    socketio.on("disconnect", function(){});

    function selectRoom(){
      console.log("select Room");
      var data = {};
      var name = document.getElementById("name_input");
      var room = document.getElementById("room_input");
      data.name = name.value;
      data.room = room.value;
      socketio.emit("roomSelect", data);
    }

    function result(data){
      if(data.result == 0){
        alert(data.msg);
      }else{
        var initArea = document.getElementById("initArea");
        initArea.setAttribute("style", "display:none");

        var chatArea = document.getElementById("chatArea");
        chatArea.setAttribute("style", "dispaly:");

        var gameArea = document.getElementById("gameArea");
        gameArea.innerHTML = data.html;
        gameArea.setAttribute("style", "display:");

        var domMsg = document.createElement('div');
        domMsg.innerHTML = new Date().toLocaleTimeString() + ' ' + data.msg;
        msgArea.appendChild(domMsg);
      }
    }

    function start(name){
      socketio.emit("connected", name);
    }

    function publishMessage(){
      var textInput = document.getElementById('msg_input');
      socketio.emit("publish", textInput.value);
      textInput.value='';
    }

    function addMessage(msg){
      var domMsg = document.createElement('div');
      domMsg.innerHTML = new Date().toLocaleTimeString() + ' ' + msg;
      //msgArea.appendChild(domMsg);
      msgArea.insertBefore(domMsg, msgArea.firstChild);
    }

    var msgArea = document.getElementById("msg");
    //var myName = Math.floor(Math.random()*100) + "さん";
    //addMessage("あなたは" + myName + "として入室しました");
    //start(myName);

  </script>
</body>
</html>