websockt 實現(xiàn)簡易聊天室

一、原生websocket


首先介紹一下實現(xiàn)即時通信方式,有如下三種:
1. 輪詢Ajax——不停詢問,浪費性能;
2.服務(wù)器響應(yīng)流:服務(wù)器不關(guān)閉連接,一次響應(yīng),一直保持連接——資源有限;
3. 使用websocket——有兼容問題

websocket是HTML5中新推出的通信協(xié)議,是在原來http協(xié)議基礎(chǔ)上進(jìn)行升級的,屬于長連接通信。
原來http協(xié)議為一問一答形式,先有請求才有響應(yīng)的機(jī)制。而websocket其底層利用了TCP協(xié)議(客戶端與服務(wù)器建立連接之后,便可以自由通信),服務(wù)器也可以主動發(fā)請求給客戶端。
注:websocket只存在于前臺,是html5帶的一種東西。后臺本就有socket

原生的websocket中:

  • 客戶端可用直接通過new Websocket('ws://xxxx/')建立通訊,通過原生的一系列事件與方法來實現(xiàn)與服務(wù)端的溝通;
  • 服務(wù)端基于net模塊(實現(xiàn)TCP協(xié)議)+crypto模塊(保證安全)來創(chuàng)建連接和監(jiān)聽通信數(shù)據(jù);
  • 服務(wù)端原生的socket實現(xiàn)十分復(fù)雜,包括:
    1. 響應(yīng)客戶端的請求報文,從請求報文中拆分出連接的版本等基本信息判斷是否需要響應(yīng);
    2. 從請求報文中解析出掩碼并根據(jù)掩碼回寫響應(yīng)報文;
    3. 最復(fù)雜的在于數(shù)據(jù)幀的解析,涉及到幀結(jié)構(gòu)的拆分,數(shù)據(jù)的加密,二進(jìn)制數(shù)據(jù)的讀寫等。
ws請求報文

websocket幀結(jié)構(gòu)

總之,原生的websocket使用起來十分不便,在實際應(yīng)用中,多使用第三方庫。

二、中間件與第三方庫


核心思想socket.io——交互方式可能通過websocket/輪詢Ajax/服務(wù)器響應(yīng)流實現(xiàn):1.服務(wù)器可主動發(fā)數(shù)據(jù)到客戶端;2.客戶端可通過服務(wù)器向客戶端發(fā)數(shù)據(jù)。

客戶端:依賴socket.io-client
  1. 引入js文件 <script src = "/socket.io.js"></script>

  2. 建立連接 const socket = io('[http://localhost:8888');](http://localhost:8888');/)

  3. 監(jiān)聽事件 socket.on(eventName, data => {});

  4. 向服務(wù)器傳遞消息 socket.emit(name, value);

服務(wù)端:依賴koa-socket
  1. 引入IO并實例化對象 const IO = require("koa-socket"); const io = new IO();

  2. io監(jiān)管app io.attach(app);

  3. 聲明事件 io.on(eventName, sock=> {});

  4. 接收客戶端消息

    io.on(name, ctx => {
        //ctx.data就是前臺發(fā)送的數(shù)據(jù)
        ctx.socket.emit(name, value);        //回應(yīng)消息,name需要與前臺對應(yīng)
    })
  1. 廣播事件
  • 廣播事件 io.broadcast(name, value)
  • 私聊推送 app._io.to(usersId).emit(name, value)
  • 加入群組 ctx.socket.socket.join(id)
  • 群組推送 ctx.socket.socket.to(groupId).emit(name, value)

三、案例——簡易聊天室

服務(wù)端:
const Koa = require("koa");
const static = require("koa-static");
const IO = require("koa-socket");

const app = new Koa();
//實例化socket.io對象
const io = new IO();
//io監(jiān)管app
io.attach(app);
//模擬用戶信息
let user = {};
//記錄用戶信息
io.on('user', ctx => {
  //存儲用戶通信id
  user[ctx.data.userName] = ctx.socket.id;
  //添加分組信息
  ctx.socket.socket.join(ctx.data.groupId);
})
io.on('msg', ctx => {
  //獲取客戶端數(shù)據(jù)并廣播
  let {to, from, content, groupId} = ctx.data;
  
  if(groupId == '0') {          //公共消息則廣播給所有人
    io.broadcast('msg', {from, content});
  } else if(to == 'all') {      //群組消息則發(fā)送給整個群組
    //向監(jiān)管部分推送消息
    ctx.socket.socket.to('0').emit('msg', {from: from, content});
    //向群組成員推送消息
    ctx.socket.socket.to(groupId).emit('msg', {from: from, content});
    //向發(fā)起人推送消息
    app._io.to(user[from]).emit('msg', {from: from, content});
  } else {                      //私聊信息則只發(fā)給特定的人
    let fromStr = from + '(私聊)';
    //向發(fā)起人推送消息
    app._io.to(user[from]).emit('msg', {from: fromStr, content});
    //私人消息則僅僅發(fā)送給對應(yīng)的人
    app._io.to(user[to]).emit('msg', {from: fromStr, content});
  }
})

//配置靜態(tài)資源文件
app.use(static('./'));
//開啟服務(wù)器
app.listen(8888, ()=>{
  console.log('The server is running...');
})
客戶端
 <!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script src="./socket.io.js" charset="utf-8"></script>
  <style media="screen">
    .chatroom {
      display: none;
    }
    .myOwnMessage {
      color: #205da1;
    }
  </style>
</head>
<body>
  <!-- 用戶信息設(shè)置窗口 -->
  <div class="setting">
    輸入你的昵稱:<input type="text" name="username">
    選擇加入的群聊組: <select class="groups" name="">
      <option value="0">江湖</option>
      <option value="1">魔法</option>
      <option value="2">物理</option>
    </select>
    <button type="button" name="button" id="setName">提交</button>
  </div>
  <!-- 聊天區(qū)域 -->
  <div class="chatroom">
    <div class="info">
      <span id="user">xxx</span>,您好!歡迎來到聊天室~
    </div>
    <!-- 信息列表 -->
    <ul id="chatList">
    </ul>
    <!-- 信息輸入?yún)^(qū)域 -->
    <textarea id="content" name="content" rows="8" cols="80"></textarea>
    <button type="button" id="submit" name="button">發(fā)送</button>
    <button type="button" id="toBoss" name="button">私聊給老板</button>
  </div>
</body>
<script type="text/javascript">
  //建立連接
  const socket = io('[http://localhost:8888');](http://localhost:8888');/)
  //監(jiān)聽事件
  socket.on('connect', data => {
    console.log('連接上了!')
  });
  socket.on('disconnect', data => {
    console.log('斷開連接了!')
  });
  //獲取元素
  let oSubmit = document.getElementById('submit');
  let oSubmitToBoss = document.getElementById('toBoss');
  let oChat = document.getElementsByClassName('chatroom')[0];
  let oSettingBox= document.getElementsByClassName('setting')[0];
  let oSetName = document.getElementById('setName');
  let oText = document.getElementById('content');
  //記錄用戶名稱與分組信息
  let username, groupId;
  //綁定設(shè)定昵稱事件
  oSetName.onclick = function () {
    //獲取昵稱
    userName = document.querySelectorAll('input[name="username"]')[0].value;
    groupId = document.querySelectorAll('.groups')[0].value;
    //隱藏昵稱設(shè)置區(qū)域
    this.parentNode.style.display = 'none';
    //顯示聊天區(qū)域并發(fā)送用戶信息
    socket.emit('user', {
      userName, groupId
    });
    document.getElementById('user').innerHTML = userName;
    oChat.style.display = 'block';
  }
  function sendMsg(toWho) {
    //獲取內(nèi)容
    let sContent = oText.value;
    oText.value = "";
    //發(fā)送通信數(shù)據(jù)
    socket.emit('msg', {
      to: toWho,
      groupId: groupId,
      from: userName,
      content: sContent
    });
  }
  //綁定事件,發(fā)送聊天信息
  oSubmit.onclick = function () {
    sendMsg('all');
  }
  //發(fā)送私聊信息
  oSubmitToBoss.onclick = function () {
    sendMsg('boss');
  }
  //監(jiān)聽服務(wù)器的廣播
  socket.on('msg', sock => {
    let oUl = document.getElementById('chatList');
    let oLi = document.createElement('li');
    //判斷是否為自己發(fā)送的消息,是則設(shè)置樣式
    if(sock.from.indexOf(userName) != -1) {
      oLi.className = 'myOwnMessage';
    }
    oLi.innerHTML = `${sock.from}:${sock.content}`;
    oUl.appendChild(oLi);
  })
</script>
</html>
  • 實現(xiàn)效果


    聊天室效果
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容