วันศุกร์ที่ 16 พฤษภาคม พ.ศ. 2557

HTML5 : node.js and WebSocket - Practical 1 Room Manage


วันนี้ทำจริงจังครับ แต่แรงพอแค่สร้างส่วนจัดการห้อง เหมือนเกมออนไลน์เลยครับมี 3 ส่วน
1. ส่วนหน้า connect server
 

2. ส่วนหน้า Room List คือดูห้องที่เราจะเข้าไปอาศัย แล้วก็สามารถสร้างห้องของตัวเองได้ครับ
 

3. ส่วนหน้า Room Waiting คือหน้าที่เราเข้าห้องแล้วรอเล่นเกมนะครับ
 

ขอไม่อธิบายโค้ดละเอียดนะครับ เยอะมาก เอาไปลองรันเล่นกันได้เลยครับ ใครที่อ่านโค้ดไม่รู้เรื่องให้อ่าน part1 ,part2, part3 ก่อนครับ แล้วก็จะอ่านของตอนนี้รู้เรื่องครับ ขอให้ความรู้อยู่คู่ทุกคนครับ 555

-------------------------------------------------------------------------

โค้ดๆ
ฝั่ง server  มีพระเอก 3 ตัวครับ

socket ทุกคนคงรู้จักแล้ว เป็น socket ใช้ในการติดต่อหากันระหว่า client กับ server

user ผมใช้เป็นตัวแทน user ครับ ตอนนี้มี 3 field คือ socketId(ไว้อ้างอิงเพื่อดึง socket), name, roomId(ใช้ระบุว่า user นี้อยู่ห้องไหน หากไม่ได้อยู่จะเป็น null)

room เป็นตัวแทนห้องครับ ถูกสร้างเมื่อ create ครับใครมา join ก็รับเอา roomId ไปใส่ให้ user ได้เลยครับ

-- การไล่โค้ด --
แบ่งเป็น 2 ส่วนโดยคั่นกลางด้วย Listener method นั่นก็คือ socketio.listen(server).on('connection' นั่นเอง
- โดย function ที่อยู่ด้านบนจะเป็นส่วนที่ listener เรียกใช้
- และ function ที่อยู่ด้านล่างจะเป็นส่วนจัดการข้อมูลเรียกว่าเป็น utility function ละกันครับ


// practical1/server.js
var http = require('http');
var formidable = require("formidable");
var socketio = require('socket.io');
var server = http.createServer(function(req, res) {
        form = new formidable.IncomingForm();
        form.parse(req, function(e, fields, files){
                res.writeHead(200, [['Content-type','text/plain'],['Content-Length',0]]);
                res.write('');
                res.end();
                //Handle sending data back through the socket to the web client
                handleServerNotice(fields);
        });
}).listen(1333, function() {
        console.log('listening on 1333');
});

var RoomStatusEnum = { WAITING: 1, PLAYING: 2 };
if(Object.freeze){
    Object.freeze(RoomStatusEnum);   
}


var sockets = new Array();
var users = new Array();
var rooms = new Array();


function registerUser(socketId,name){
    var user = new Object();
    user.socketId = socketId;
    user.name = name;
    user.roomId = null;
    users.push(user);
   
    var socket = socketManage.getSocket(socketId);
    socket.emit("registerUser",JSON.stringify(user));
   
    userList();
    roomList();
}

function disconnectUser(socketId){
     var user = userManage.getUser(socketId);
    console.log('disconnectUser = ' + JSON.stringify(user));
    for(var i=0;i<users.length;i++){
        var queryuser = users[i];
        var socket = socketManage.getSocket(queryuser.socketId);
        if(socket != null){
            socket.emit("disconnectUser",JSON.stringify(user));
        }
    }
   
    if(user.roomId != null){
        leaveRoom(user.roomId);
    }
   
    userManage.removeUser(socketId);
    socketManage.removeSocket(socketId);
    userList();
}

function userList(){
    for(var i=0;i<users.length;i++){
        var user = users[i];
        var socket = socketManage.getSocket(user.socketId);
        if(socket != null){
            socket.emit("userList",JSON.stringify(users));
        }
    }
}

function createRoom(socket,roomName){
        var room = new Object();
        room.id = socket.manager.generateId();
        room.name = roomName;
         var user = userManage.getUser(socket.id);
        room.ownerId = user.socketId;
        room.ownerName = user.name;
        room.status = RoomStatusEnum.WAITING;
       
        user.roomId = room.id;
       
       
        rooms.push(room);
       
        var socket = socketManage.getSocket(user.socketId);
        socket.emit("createRoom",JSON.stringify(room));
       
        //updateRoom(room.id); // update room status
        roomList(); // notify user outside room
        userRoomList(socket,room.id); // notify user inside room
}

function joinRoom(socket,roomId){
     var room = roomManage.getRoom(roomId);
   
    var user = userManage.getUser(socket.id);
    user.roomId = room.id;
   
    var socket = socketManage.getSocket(user.socketId);
    socket.emit("joinRoom",JSON.stringify(room));
   
    //updateRoom(room.id); // update room status
    userRoomList(socket,room.id); // notify user inside room
}

function leaveRoom(socket,roomId){
    var room = roomManage.getRoom(roomId);
    var user = userManage.getUser(socket.id);
   
    // leave
    user.roomId = null;
   
    var userRooms = userManage.getUserRoom(roomId);
   
    // no anybody in room
    if(userRooms.length == 0){
        roomManage.removeRoom(roomId);
    }else{
        // user is owner then grant the next user to be the next owner and update room status
        if(room.ownerId == user.socketId){
            room.ownerId = userRooms[0].socketId;
            room.ownerName = userRooms[0].name;           
            updateRoom(roomId);
        }
       
    }
   
    var socket = socketManage.getSocket(user.socketId);
    socket.emit("leaveRoom",JSON.stringify(user));
   
    userRoomList(socket,room.id); // notify user inside room
   
    roomList();
}

function updateRoom(roomId){
    var room = roomManage.getRoom(roomId);
    var userRooms = userManage.getUserRoom(roomId);
    for(var i=0;i<userRooms.length;i++){
        var userRoom = userRooms[i];
        var socket = socketManage.getSocket(userRoom.socketId);
        if(socket != null){
            socket.emit("updateRoom",JSON.stringify(room));
        }
    }
}

function roomList(){
    for(var i=0;i<users.length;i++){
        var user = users[i];
        // send to user that not in the room
        if(user.roomId == undefined || user.roomId == null){
            var socket = socketManage.getSocket(user.socketId);
            if(socket != null){
                socket.emit("roomList",JSON.stringify(rooms));
            }
        }       
    }
}

/**
 show user in the room
*/
function userRoomList(socket,roomId){
    var userRooms = userManage.getUserRoom(roomId);
   
    for(var i=0;i<userRooms.length;i++){
        var userRoom = userRooms[i];
        var socket = socketManage.getSocket(userRoom.socketId);
        if(socket != null){
            socket.emit("userRoomList",JSON.stringify(userRooms));
        }
    }
}

// Listener method
socketio.listen(server).on('connection', function(socket){
        console.log("generatedId = " + socket.manager.generateId());
       
        sockets.push(socket);
       
        socket.on('registerUser', function (data) {
            console.log(socket.id + " registerUser = " + data);
            var user = JSON.parse(data);
            registerUser(socket.id,user.name);
        });
       
        socket.on('userList', function (data) {
            userList();
        });
       
        socket.on('createRoom', function (data) {
            var room = JSON.parse(data);
            createRoom(socket,room.name);
        });
       
        socket.on('joinRoom', function (data) {
            var room = JSON.parse(data);
            joinRoom(socket,room.id);
        });
       
        socket.on('leaveRoom', function (data) {
            var room = JSON.parse(data);
            //console.log("leaveRoom = " +data);
            leaveRoom(socket,room.id);
        });
       
        socket.on('roomList', function (data) {
            roomList();
        });
       
        socket.on('userRoomList', function (data) {
            var room = JSON.parse(data);
            userRoomList(room.id);
        });

        socket.on('disconnect', function(data){
            console.log('disconnect socket id', socket.id);
            disconnectUser(socket.id);
            //socketManage.removeSocket(socket.id);
        });


});

/***************************************************************************
 User Manage
****************************************************************************/
 var userManage = new Object();

/**
    @Description
    find socket index by socketId
     @Return
     index of Socket in sockets array
**/
 userManage.getUserIndex = function(socketId){
        var selectIndex = -1;
    for(var i=0;i<users.length;i++){
        var user= users[i];
        if(user.socketId == socketId){
            selectIndex = i;
            break;
        }
    }
    return selectIndex;
 };

 /**
     @Description
    find user in the room
     @Return
     all user in the room
 **/
 userManage.getUserRoom = function(roomId){
     var userRooms = new Array();
    for(var i=0;i<users.length;i++){
        var user= users[i];
        if(user.roomId == roomId){
            userRooms.push(user);
        }
    }
    return userRooms;
 };


/**
    @Description
    get socket by socketId
    @Return
    Socket or null if not found
**/
userManage.getUser = function(socketId){
    var selectIndex = userManage.getUserIndex(socketId);
    if(selectIndex != -1){
        return users[selectIndex];   
    }else{
        return null;   
    }
};

/**
    @Description
    remove socket by socketId
**/
 userManage.removeUser = function(socketId){
        var selectIndex = userManage.getUserIndex(socketId);
    if(selectIndex != -1){
        users.splice(selectIndex,1);
    }
};


/***************************************************************************
 Room Manage
****************************************************************************/
 var roomManage = new Object();

/**
    @Description
    find room index by roomId
     @Return
     index of room in rooms array
**/
  roomManage.getRoomIndex = function(roomId){
        var selectIndex = -1;
    for(var i=0;i<rooms.length;i++){
        var room= rooms[i];
        if(room.id == roomId){
            selectIndex = i;
            break;
        }
    }
    return selectIndex;
 };


/**
    @Description
    get room by roomId
    @Return
    Room or null if not found
**/
roomManage.getRoom = function(roomId){
    var selectIndex = roomManage.getRoomIndex(roomId);
    if(selectIndex != -1){
        return rooms[selectIndex];   
    }else{
        return null;   
    }
};

/**
    @Description
    remove socket by socketId
**/
 roomManage.removeRoom = function(roomId){
        var selectIndex = roomManage.getRoomIndex(roomId);
    if(selectIndex != -1){
        rooms.splice(selectIndex,1);
    }
};




/***************************************************************************
 Socket Manage
****************************************************************************/
 var socketManage = new Object();

/**
    @Description
    find socket index by socketId
     @Return
     index of Socket in sockets array
**/
 socketManage.getSocketIndex = function(socketId){
        var selectIndex = -1;
    for(var i=0;i<sockets.length;i++){
        var socket = sockets[i];
        if(socket.id == socketId){
            selectIndex = i;
            break;
        }
    }
    return selectIndex;
 };


/**
    @Description
    get socket by socketId
    @Return
    Socket or null if not found
**/
socketManage.getSocket = function(socketId){
    var selectIndex = socketManage.getSocketIndex(socketId);
    if(selectIndex != -1){
        return sockets[selectIndex];   
    }else{
        return null;   
    }
};

/**
    @Description
    remove socket by socketId
**/
 socketManage.removeSocket = function(socketId){
        var selectIndex = socketManage.getSocketIndex(socketId);
    if(selectIndex != -1){
        sockets.splice(selectIndex,1);
    }
};

//Emit a message to the client that we received a blank message from the php script. (Via POST)
function handleServerNotice(data){
        console.log("HTML Message Received: ", data);
}

//-------------------------- client -------------------//
// practical1/worker.js อันนี้สวยอยู่แล้วไม่ต้องบอกอะไร 55
importScripts('js/socket.io.js');
var socket;
var user;
var room;
var game;
self.onmessage = function (ev) {
   
      var data = ev.data;
    switch (data.cmd) {
       
  // connect
    case 'connect':
    {
       
        self.postMessage({'type': 'status', 'msg': 'Client Connect '});
        user = null;
        room = null;
        game = null;
       
        var url = data.msg;
        if(socket !=  undefined){
            socket.socket.connect();
        }else{
            socket = io.connect(url);
           
            socket.on('done', function () {
                self.postMessage({'type': 'status', 'msg': 'done!'});
              });
           
              socket.on('connect_failed', function () {
                self.postMessage({'type': 'status', 'msg': 'connect failed'});
              });
           
              socket.on('error', function () {
                self.postMessage({'type': 'status', 'msg': 'error'});
              });
             
       
              // listener registerUser send back from server
              socket.on('registerUser', function (data) {
                  user = JSON.parse(data);
                  self.postMessage({'type': 'registerUser', 'msg': data});
              });
             
              // listener disconnectUser send back from server
              socket.on('disconnectUser', function (data) {
                var user = JSON.parse(data);
                self.postMessage({'type': 'status', 'msg': 'disconnectUser '+user.name}); // send from worker to web page
              });
       
              // listener userList send back from server
              socket.on('userList', function (data) {
                self.postMessage({'type': 'userList', 'msg': data}); // send from worker to web page
              });
             
              socket.on('updateRoom', function (data) {
                  room = JSON.parse(data);
                  self.postMessage({'type': 'updateRoom', 'msg': data});
              });
             
              socket.on('createRoom', function (data) {
                  room = JSON.parse(data);
                  self.postMessage({'type': 'createRoom', 'msg': data});
              });
             
              socket.on('joinRoom', function (data) {
                  room = JSON.parse(data);
                  self.postMessage({'type': 'joinRoom', 'msg': data});
              });
             
              socket.on('leaveRoom', function (data) {
                  //user = JSON.parse(data);
                  room = null;
                  self.postMessage({'type': 'leaveRoom', 'msg': data});
              });
             
              socket.on('roomList', function (data) {
                self.postMessage({'type': 'roomList', 'msg': data}); // send from worker to web page
              });
             
              socket.on('userRoomList', function (data) {
                self.postMessage({'type': 'userRoomList', 'msg': data}); // send from worker to web page
              });
       
             
        }
        
   
     

    }
     
      break;
   
   
    // stop
    case 'disconnect':
        self.postMessage({'type': 'status', 'msg': 'Client Disconnect '});
      socket.disconnect();
     
      break;
   
    // send to Server
    case  'registerUser':
            socket.emit('registerUser',data.msg);
    break;
   
    case  'createRoom':
            socket.emit('createRoom',data.msg);
    break;
   
    case  'joinRoom':
            socket.emit('joinRoom',data.msg);
    break;
   
    case  'leaveRoom':
            socket.emit('leaveRoom',JSON.stringify(room));
    break;
   
   
   
    default:
      //self.postMessage('Unknown command: ' + data.msg);
  };

}


// practical1/client.php ผมใช้ jquery ด้วยนะครับ ใช้มี version ไหนอยู่ก็ใช้ได้เลยครับ
<!DOCTYPE HTML>
<html>
<head>
<title>NodeJS Practical1</title>
<script type="text/javascript" src="js/jquery_1.9.js"></script>
<script>
var worker = new Worker('worker.js');

worker.onmessage = function (event) {
    //alert(event.data);
    var data = event.data;
    switch (data.type) {
        case 'status' :
            document.getElementById('result').innerHTML = document.getElementById('result').innerHTML+data.msg+"\r\n";
            break;
        case 'data'  :
        ;
        break;
        case 'userList'  :
            var users = JSON.parse(data.msg);
            var response = '';
            for(var i=0;i<users.length;i++){
                var user = users[i];
                response += user.name+" ,socketId = " + user.socketId+"<br/>";
            }
            document.getElementById('response').innerHTML = response;
        break;

        case 'registerUser' :
        {
            var user = JSON.parse(data.msg);
            document.getElementById('displayName').innerHTML = user.socketId + ' - ' +user.name;

            // disable connectBtn   
            var connectBtn = document.getElementById('connectBtn');
            var disconnectBtn = document.getElementById('disconnectBtn');
       
            connectBtn.disabled = true;
            disconnectBtn.disabled = false;
           
            // go to room list step
            document.getElementById('connectSection').style.display = 'none';
            document.getElementById('roomSection').style.display = 'block';
           
            break;
        }
       
        case 'createRoom' :
        case 'joinRoom' :
        {
            var room = JSON.parse(data.msg);
            $('#roomWaitingSection .roomName').html(room.id +' - '+room.name);
            $('#roomWaitingSection .roomOwner').html(room.ownerId + ' - ' +room.ownerName);
           
            // go to room waiting step
            document.getElementById('roomSection').style.display = 'none';
            document.getElementById('roomWaitingSection').style.display = 'block';
           
            break;
        }
       
        case 'leaveRoom'  :
        {
            document.getElementById('roomSection').style.display = 'block';
            document.getElementById('roomWaitingSection').style.display = 'none';
            break;
        }
       
        case 'roomList'  :
        {
                var rooms = JSON.parse(data.msg);
                roomSection.updateRoomList(rooms);
            break;
        }
       
        case 'userRoomList'  :
        {
                var userRooms = JSON.parse(data.msg);
                roomSection.updateUserRoomList(userRooms);
            break;
        }
       
    };

};

var roomSection = new Object();

// Room List
roomSection.templateRow = function(row,rowItem){

    if(rowItem.id != undefined){
        row.find('.roomId').text(rowItem.id+".");
    }else{
        row.find('.roomId').text("");
    }
   
   
    if(rowItem.name != undefined){
        row.find('.roomName').text(rowItem.name);
    }else{
        row.find('.roomName').text("");
    }
   
    if(rowItem.ownerId != undefined){
        row.find('.roomOwner').text(rowItem.ownerId +" - " + rowItem.ownerName);
    }else{
        row.find('.roomOwner').text("");
    }
   
   
    row.find('.joinBtn').attr('onclick',"roomSection.joinRoom('"+rowItem.id+"');");
   
    row.addClass("table_row");
    return row;
};

roomSection.updateRoomList = function(rooms){
    var root = $('.roomList');
    root.find('.table_row').remove();
   
    for(var item = 0;item<rooms.length;item++ ){
        var newRow = root.find('.template_row').clone().removeClass('template_row');
        roomSection.templateRow(newRow,rooms[item]).appendTo(root).fadeIn();
    }
};

// User Room List
roomSection.templateUserRoomRow = function(row,rowItem){

    if(rowItem.socketId != undefined){
        row.find('.userId').text(rowItem.socketId+".");
    }else{
        row.find('.userId').text("");
    }
   
   
    if(rowItem.name != undefined){
        row.find('.userName').text(rowItem.name);
    }else{
        row.find('.userName').text("");
    }
   
    row.addClass("table_row");
    return row;
};


roomSection.updateUserRoomList = function(userRooms){
    var root = $('.userRoomList');
    root.find('.table_row').remove();
   
    for(var item = 0;item<userRooms.length;item++ ){
        var newRow = root.find('.template_row').clone().removeClass('template_row');
        roomSection.templateUserRoomRow(newRow,userRooms[item]).appendTo(root).fadeIn();
    }
};

roomSection.createRoom = function(){
   
    var roomName = document.getElementById('roomName').value;
    if(roomName == ''){
        alert("Please enter room name");
        return;
    }

    var room = new Object();
    room.name = roomName;
    worker.postMessage({'cmd': 'createRoom', 'msg': JSON.stringify(room)});
};

roomSection.joinRoom = function(roomId){
    var room = new Object();
    room.id = roomId;
    worker.postMessage({'cmd': 'joinRoom', 'msg': JSON.stringify(room)});
};

roomSection.leaveRoom = function(){
    worker.postMessage({'cmd': 'leaveRoom', 'msg': ''});
};


function connect(){
    var name = document.getElementById('name').value;
    if(name == ''){
        alert("Please enter  your name");
        return;
    }

    worker.postMessage({'cmd': 'connect', 'msg': '127.0.0.1:1333'}); // connect at 127.0.0.1:1333
   
    var user = new Object();
    user.name = name;
    worker.postMessage({'cmd': 'registerUser', 'msg': JSON.stringify(user)});

}

function disconnect(){
worker.postMessage({'cmd': 'disconnect', 'msg': ''});
    var connectBtn = document.getElementById('connectBtn');
    var disconnectBtn = document.getElementById('disconnectBtn');
   
    connectBtn.disabled = false;
    disconnectBtn.disabled = true;
}


</script>

</head>
<body>
<div style="height:200px;">
    <div style="float:left">
<p style="vertical-align:text-top">Server Status: <br/><textarea id="result"  cols="80" rows="8" ></textarea></p>
</div>
<div style="float:right;width:400px;border:1px solid;height:150px;overflow:scroll">
User in Server : <br/>
<span id="response"></span>
</div>
</div>

<br/><br/>
<div>

<div id="displayName" style="font-weight:bold;color:blue"></div>
<br/>

<div id="connectSection">
Name : <input type="text" id="name" name="name" /> <br/>
<input onClick="connect();" type="button" name="connectBtn" id="connectBtn" value="Connect" />
<input onClick="disconnect();" type="button" name="disconnectBtn" id="disconnectBtn" value="Disconnect" disabled="disabled"/>
</div>

<div id="roomSection" style="display:none;">
<div style="font-weight:bold;color:blue">Room List Page</div>
    <br/>
    New Room : <input type="text" id="roomName" name="roomName" />
<input onClick="roomSection.createRoom();" type="button" name="createRoom" id="createRoom" value="Create Room" />
<br/><br/>
    <table class="roomList" border="1" cellspacing="0">
        <tr style="font-weight:bold;">
            <td width="120" align="center">Room Id</td>
            <td width="150" align="center">Room Name</td>
            <td width="200" align="center">Room Owner</td>
            <td width="80">&nbsp;</td>
        </tr>
        <tr class="template_row" style="display:none">
            <td class="roomId" align="left">1234567890</td>
            <td class="roomName" align="left">Test</td>
            <td class="roomOwner" align="left">12345678 - tester</td>
            <td align="center"><input class="joinBtn" type="button" value="Join" style="width:60px;" /></td>
        </tr>
    </table>
</div>

<div id="roomWaitingSection" style="display:none;">
    <div style="font-weight:bold;color:blue">Room Waiting Page</div>
    <br/>
    Room Name : <span class="roomName"></span>
    <br/>
    Room Owner : <span class="roomOwner"></span>
    <br/><br/>
    User :
    <table class="userRoomList" border="1" cellspacing="0">
        <tr style="font-weight:bold;">
            <td width="120" align="center">User Id</td>
            <td width="150" align="center">User Name</td>
        </tr>
        <tr class="template_row" style="display:none">
            <td class="userId" align="left">1234567890</td>
            <td class="userName" align="left">Test</td>
        </tr>
    </table>
    <br/>
    <input type="button" value="Start Game" /> <input type="button" value="Leave Room" onClick="roomSection.leaveRoom();"/>
   
</div>

</div>
</body>
</html>
สุดท้ายมารันกันครับ
อย่าลืม socket.io.js และ socket.io.min.js เอามาใส่ในโฟลเดอร์ js ด้วยนะครับ เอามาจาก module client ของ node.js นะครับ
รัน server ด้วย node server.js
ใครรันได้แล้วรู้เรื่องมากขึ้นก็แจ้งมาได้นะครับ 555

1 ความคิดเห็น: