วันอังคารที่ 6 พฤษภาคม พ.ศ. 2557

HTML5 : node.js and WebSocket - Part 3 Socket ID and maintain socket state

บทนี้จะมาพูดถึงคุณลักษณะของการจัดการ client socket ที่มาติดต่อนะครับ

1 หน้าบราวเซอร์ = 1 socket นะครับ เช่นใช้ chrome เปิดขึ้นมา 2 หน้า ทั้งสองหน้าก็จะเป็นคนละ socket กันครับ ซึ่งการอ้างถึงแต่ละหน้าว่าจะอ้างอิงด้วยอะไรดี ซึ่งนั่นก็คือประเด็นของวันนี้ครับ socket.id นั่นเอง
socket.id เป็น id ที่จะ unique คือไม่ซ้ำกันเลยเราสามารถใช้ในการอ้างอิงเพื่อส่งข้อมูลกลับ หรือสั่งให้ disconnect ได้ครับ

maintain socket state
มาดูว่าเจ้า server เราใช้วิธีการไหนในการเช็คว่า socket ตัวไหน alive หรือใช้งานอยู่ ตัวไหนปิดหน้าเว็บไปแล้วครับ ซึ่งเราเรียกว่า heartbeat packet ครับ จังหวะหัวใจ 555 เป็นบี้เดอะสตาร์แล้ว ซึ่งจะมีการ request ไปกา client socket ทุกๆ ช่วงเวลา แล้วถ้าไม่มี packet ตอบกลับก็จะเข้าสู่สถานะ disconnect ทันทีครับ

โอเค มาดูการทำงานของ heartbeat packet กันครับ



Alive Condition -> ที่ผมครอบสีเขียวครับ เป็น heartbeat ที่ได้รับการตอบกลับจาก client
set heartbeat time out for client 855pstHg3R8L9RKC01EQ (socket id)
got heartbeat packet
แสดงว่าได้รับกลับมาครับ

Disconnect Condition -> สีแดงเป็นสถานะของการ disconnect การ heartbeat packet ครับ ซึ่งจะเกิดขึ้นเมื่อปิดหน้า page ที่ connect socket อยู่ครับ ซึ่งถ้า server ส่งไปแล้วไม่ได้รับตอบกลับก็จะเข้าสถานะ disconnect ทันทีครับ
set heartbeat interval for client 855pstHg3R8L9RKC01EQ (socket id)
transport end
set close timeout for client 855pstHg3R8L9RKC01EQ
cleared close timeout for client
cleared heartbeat interval for client 855pstHg3R8L9RKC01EQ

------------------------------------------------------------------------------------
จบ section ของเนื้อหา มาเริ่มโค้ดกันดีกว่าครับ เด๋วเข้าใจเองครับ
 

วันนี้เราจะมาเล่นกันเรื่อง connect กับ Disconnect กันครับ

มาดูฝั่ง server.js ของเรากัน
var sockets = new Array();
var users = new Array();
เราจะสร้าง sockets ไว้สำหรับเก็บ socket ที่ client connect เข้ามาครับ
ส่วน users สำหรับเก็บข้อมูล user คู่กับ socketId ที่ user คนนั้นใช้
socket.on('disconnect', function(data)
เป็น event ที่จะเข้าเมื่อเกิดการ Disconnect ทั้งจาก heartbeat packet และการส่ง disconnect packet มาจาก client ซึ่งเราจะดักแล้ว remove user ออกจาก array ที่เราใส่ไว้ครับ

ส่วนจัดการข้อมูล ผมใช้ javascript Object ที่ชื่อว่า userManage ครับใช้สำหรับการจัดการกับ user ใน server และ socketManage ใช้จัดการกับข้อมูล socket ที่ติดต่อเข้ามาซึ่งเราจะใช้ socketId ในการอ้างอิงนะครับ
var userManage = new Object();
userManage.getUserIndex = function(socketId) // get array index from socketId
userManage.getUser = function(socketId) // get user object from array by socketId
userManage.removeUser = function(socketId) // remove user object from array by socketId


var socketManage = new Object();
socketManage.getSocketIndex = function(socketId) // get array index from socketId
socketManage.getSocket = function(socketId) // get socket object from array by socketId
socketManage.removeSocket = function(socketId) // remove socket object from array by socketId

ฝั่ง client worker.js ครับ
socket = io.connect(url); // ใช้สำหรับการ connect ครั้งแรก
socket.socket.connect(); // ใช้สำหรับ connect อีกครั้งหลังจาก disconnect ไปแล้วซึ่งจะได้ socketId ใหม่
socket.disconnect(); // ใช้สำหรับส่ง disconnect packet ไปยัง server เพื่อ disconnect

------------------------------------------------------------------
ตัวอย่างโค้ดวันนี้ครับ
//----------- Server Side ---------------//
// part3/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 sockets = new Array();
var users = new Array();

function registerUser(socketId,name){
    var user = new Object();
    user.socketId = socketId;
    user.name = name;
    users.push(user);
   
    //var socket = socketManage.getSocket(socketId);
    //socket.emit("registerUser",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("registerUser",JSON.stringify(user));
        }
    }
   
   
    userList();
}

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));
        }
    }
   
    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));
        }
    }
}

socketio.listen(server).on('connection', function(socket){
        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('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
    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);
    }
};




/***************************************************************************
 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);
}

// end  part3/server.js

//----------- Client Side ---------------//
อย่าลืมเอา socket.io.js ที่เป็น client script มาใส่ใน project ด้วยนะครับ

//----------------  part3/worker.js

importScripts('js/socket.io.js');
var socket;
self.onmessage = function (ev) {
   
      var data = ev.data;
    switch (data.cmd) {
       
  // connect
    case 'connect':
    {
       
        self.postMessage({'type': 'status', 'msg': 'Client Connect '});
       
        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) {
                var user = JSON.parse(data);
                self.postMessage({'type': 'status', 'msg': 'registerUser '+user.name}); // send from worker to web page
              });
       
              // 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
              });
        }
        
   
     

    }
     
      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;
   
   
   
    default:
      //self.postMessage('Unknown command: ' + data.msg);
  };

}

// end worker.js

//----------------  part3/client.php
<!DOCTYPE HTML>
<html>
<head>
<title>NodeJS Part 3</title>

<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;

       

    };


};


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)});

    var connectBtn = document.getElementById('connectBtn');
    var disconnectBtn = document.getElementById('disconnectBtn');

    connectBtn.disabled = true;
    disconnectBtn.disabled = false;
}

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>
<p style="vertical-align:text-top">Server Status: <br/><textarea id="result"  cols="80" rows="8" ></textarea></p>

<!--
<input onclick="connect()" type="button" value="Connect" /> <input onClick="disconnect();" type="button" value="Disconnect" />
-->
<br/><br/>
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"/>

<br/>
<br/>
User in Server : <br/>
<span id="response"></span>
<br/>
<br/>

</body>
</html>

//------------------- Running -----------------//
อย่าลืมเปิด server ด้วยคำสั่ง node server.js นะครับ

ไม่มีความคิดเห็น:

แสดงความคิดเห็น