Node.js Web實時聊天室 製作

  今天要來介紹即時聊天室的製作,就像上面的圖一樣聊天室的介面大概長這個樣子,先大致分析一下這個聊天室的實作部分,分為(如下圖): 1.使用者登入 2.聊天室成員列表 3.文字傳輸 4.socket.io骨架,聊天室訊息傳輸主要是靠socket.io這個套件,這個套件有很多功能:實時分析、及時訊息傳輸機制、二進位的流傳輸(串流)-Binary Stream、文件共用編輯(Document collarboration),官網傳送門 socket.io

  socket.io的傳輸原理,就像上面server和client那2段code一樣,在client裡面有用socket.emit('message', msg); 發送訊息,注意第1個參數message是代表這一條連線的名稱,你可以自己決定要什麼名稱,這就像一條電話線,由client端打過去,server端也要有人接收,接收的code就像socket.on('message', function(msg){});第1個參數的message一定要一樣才能收到client端發的msg訊息,就這樣一發一收來傳輸資料。

  關於主要的訊息傳輸官網有sample code這裡不解釋,要講的只有server端的io.emit、socket.emit還有socket.broadcast.emit三種差別在於:

   io.emit
   發送給所有使用者端(包括自己)
   socket.emit
   只發送給發送該訊息的使用者自己
   socket.broadcast.emit
   發送給除了自己以外的用戶(使用者上下線的系統訊息、群體聊天將自己的訊息傳給其他使用者)
  Javascript部分除了bootstrap的樣式模板套件(CSS)跟JQuery,其他會用到的套件有sco.js的Message函數、Messenger(HubSpot 弹框组件库),還有underscore,這些都可以在Bootstrap網站bootcss.com看到,主要整理如下:
  1.sco.js的Message函數: 用來顯示用戶上下線的系統訊息
  2.Messenger(HubSpot 弹框组件库):私人訊息的訊息顯示套件(左下角)
  3.underscore的_.findWhere的函數:尋找socket id《每一條連線(連進來的使用者)獨一無二的id像是guid或者uuid用來辨識使用者的key》
  4.頁面剛開始輸入暱稱的modal窗口(周圍灰暗): 用來顯示使用者在輸入某些資料時候(暱稱、私人訊息)
  記得再導入Bootstrap跟JQuery的時候要先引入JQuery再引入bootstrap.js(像下面code這樣),不然modal函數會被覆蓋,就會無法使用!!

modal視窗的引入:

  //Html code 以這個順序引入js library,modal()才能正確執行
  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js'></script>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js'></script>
  //modal視窗的顯示與關閉,跟jquery的$('element').show()、$('element').hide()很像
  //詳細的使用,請看這裡 http://v4-alpha.getbootstrap.com/components/modal/
  $('#userInfoModal').modal('show');
  $('#userInfoModal').modal('hide');
  之後引入各個js或CSS、要記得看console.log(按F12後點Console),看看裡面有沒有紅字錯誤訊息,在依照錯誤來debug。

CSS的引入:

  因為我們使用的是node.js,一般來講node.js寫的server code不會因為在html用link標籤引用函數庫就主動去導入css的library,而是要在server端加上下面這行code才會去讀取這些靜態檔案。

//node js server side code
//導入css資料夾裡的東西
//前面的參數 "/css" 是html在href引用的時候要輸入的路徑
//例如改成「'/project'」之類的,那在index.html上就要改成
//「<link rel="stylesheet" href="/project/index.css" type="text/css" />」
app.use('/css', express.static('css'));
//後面的 express.static('css')是真實的資料夾路徑名稱
//而在這個專案因為是用線上開發,所以就只有寫像下面這樣(都放在同一個路徑下):
/*引入靜態檔案像css檔之類的,不先設定會無法讀取css檔*/
app.use(express.static(__dirname + "/"));
  然後其他的套件的使用說明在官網應該都有也有展示,這邊只就我寫的code做展示跟部分解釋

Messenger彈出式框框(私人訊息)部分:

 //client side code
 //extraClasses是設定私人訊息框框顯示的位置
 //theme:設定訊息框框的外觀有5種主題: 
 /*
   Flat
   Future
   Block
   Air
   Ice
 */
 //詳細請看Github Demo:   http://github.hubspot.com/messenger/docs/welcome/
  Messenger.options = {
    extraClasses: 'messenger-fixed messenger-on-bottom messenger-on-right',
    theme: 'future'
    };

   //message是你要顯示的訊息
   //showCloseButton是決定要不要顯示關閉彈出式框框的按鈕(X)
  Messenger().post({
    message: "<a href=\"javascript:showSetMsgToOne(\'" + msgObj.from.name + "\',\'"  + msgObj.from.id + "\');\">" +  msgObj.from.name + entries.UILanguage.msgFrom + msgObj.msg +  "</a>",
    showCloseButton: true
  });

sco.message.js (系統訊息:聊天室成員上下線部分):

 //client side code
 //聊天室成員上線(綠色浮出框)
 function addMsgFromSys(msg)
{
  $.scojs_message(msg + entries.UILanguage.loginText, $.scojs_message.TYPE_OK);
}
//聊天室成員離線(紅色浮出框)
function disconnectFromSys(UserObj)
{
  $.scojs_message(UserObj.name + entries.UILanguage.logoutText, $.scojs_message.TYPE_ERROR);
}

圖片即時傳送部分(Base 64):

 //Base 64圖片傳送
 $('#imgUpload').on('change', function()
    {
      if(this.files.length !== 0)
      {
        var file = this.files[0];
        reader = new FileReader();
        if(!reader)
        {
          alert("Your browser doesn\'t support fileReader !");
          return;
        }
        reader.onload = function(e)
        {
          var msgObj = {
            from: userSelf,
            img: e.target.result
          };
          socket.emit('sendImageToAll', msgObj);
          addImgFromUser(msgObj, true);
        };
        reader.readAsDataURL(file);
      }
    });
  最後是server的所有code,以及需要npm 安裝的套現貨dependencies

dependencies部分:

//這邊是npm要安裝的套件(middleware)
//請使用npm install {middleware name}
//如果是使用jade模板,而不是html時,記得引入jade,我這邊是都有先引入,就算沒用到也方便
dependencies": {
        "express": "^4.12.4",
        "body-parser": "^1.15.2",
        "cookie-parser": "^1.4.3",
        "jade": "^1.11.0",
        "morgan": "^1.7.0",
        "serve-favicon": "^2.3.0",
        "socket.io": "^1.5.0",
        "underscore": "^1.7.0"
    }
//node js real-time chatroom server code
var express = require('express');
var underscore = require('underscore');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io').listen(http);
//app.get()第1個參數是網頁路徑,請自己決定
app.get('/chat', function(req, res){
  res.sendFile(__dirname + '/chat.html');
});

/*
  user list
  Format:
  {
    {
      name:'',
      img:'',
        socketId:''
    }
  }
*/
var userList = [];

io.sockets.on('connection', function(socket){
  console.log('a user connected');
  io.emit('online', '');

  socket.on('chat message', function(msg){
    console.log(msg);
    io.emit('chat message', msg);
  });
  //login function
  socket.on('login', function(user)
  {
    // console.log("someone login");
    /*
    socket id:
    每一個socket連線之後,伺服器端都會為他創建一個id,這一個id是一個伺服器端永遠不會重複的id,這個id可以理解成我們在操作資料的時候的guid,uuid
    */

    user.id = socket.id;
    userList.push(user);
    console.log(userList);
    //send the user list to all client
    io.emit('userList', userList);
    //send the client information to clent
    socket.emit('userInfo', user);
    //send login info to all. 原發送者不收到消息*
    socket.broadcast.emit('loginInfo', user.name);
  });

  socket.on('toAll', function(msgObj)
  {
    /*
      format:{
        from:{
          name:'',
          img:'',
          id:''
        },
        msg:''
      }
    */
    socket.broadcast.emit('toAll', msgObj);
  });

  //sendImageToAll
  socket.on('sendImageToAll', function(msgObj)
  {
    /*
      format:{
        from:{
          name:'',
          img:'',
          id:''
        },
        img:''
      }
    */
    // console.log(msgObj);
    socket.broadcast.emit('sendImageToAll', msgObj);  
  });

  //send to one
  socket.on('toOne', function(msgObj)
  {
    /*
      format:{
        from:{
          name:'',
          img:'',
          id:''
        },
        to:'', // socketid
        msg:''
      }
    */
    //var toSocket = _.findWhere(socketList, {id:msgObj. to});
    //io.sockets.sockets 找到所有連接到伺服器的使用者的集合
    //_.findWhere   引用到underscore.js 的函數庫
    var toSocket = underscore.findWhere(io.sockets.sockets, {id:msgObj.to});
    console.log(toSocket);
    toSocket.emit('toOne', msgObj);
  });

  socket.on('disconnect', function()
  {
    console.log("someone disconnect");
    var index = -1
//在userList裡面尋找下線使用者,並存入變數,並無效化這個成員的Object(userList裡面的元素)
    for(var user in userList)
    {
      if(socket.id === userList[user].id)
      {
        index = userList[user];
        userList[user] = undefined;
      }
    }
//將無效離線使用者過濾掉
    userList = userList.filter(function(user)
    {
      return (user !== undefined);
    });
    var removeUser = index;
    //console.log(removeUser);
    // console.log(userList.length);
    io.emit('offline', removeUser);
    //記得如果未登入或重新連線時,因為沒有login,這個值就會是-1,這邊設條件讓有登入的使用者在離線時,才更新聊天室成員列表
    if(removeUser !== -1)
      io.emit('updateUser', userList);

  });  
});

http.listen(3000);
  這邊是前端的聊天室UI展示:聊天室UI

**P.S. / Reference:  sco.message.js
           基于Node js的web实时聊天室项目
           Messenger
           將圖片編碼成字串:MIME與Base64編碼
           

results matching ""

    No results matching ""