websocket实现多用户一对一实时聊天

在线聊天模实现

项目是基于node.js服务器搭建的简易双向通信网页,实现了实时更新在线人数以及用户间即时通讯的功能。

1、websocket连接创建

基于后台websocket地址创建简易的连接,并且当websocket断开后自动重新连接。

let lockReconnect = false // 是否允许重新连接
let timeout = 1000 * 20 // 心跳间隔
let reTimeout = 1000 * 10 // 重连间隔
let timeoutTimer = null // 心跳定时器
let reTimeoutTimer = null // 重连定时器
let webSocket = null
let uid = new Date().getTime() // 图方便,将时间戳当作用户id
// 创建websocket连接
function createWebSocket () {
  let webSocket = new WebSocket('ws://localhost:5001/' + uid)
  webSocket.binaryType = 'arraybuffer';
  webSocket.onerror = function () {
    console.log('连接失败...')
    reConnect()
  }
  // websocket收到信息的处理逻辑
  webSocket.onmessage = function (event) {
    if (event.data) {
      let res = JSON.parse(event.data)
      console.log(res)
    }
  }
  webSocket.onclose = function (event) {
    console.log('连接已关闭...')
    console.log('websocket 断开: ' + event.code + ' ' + event.reason + ' ' + event.wasClean)
    reConnect()
  }
}
// 重连
function reConnect () {
  if (lockReconnect) {
    return
  }
  lockReconnect = true
  // 没连接上会一直重连,设置延迟避免请求过多
  reTimeoutTimer && clearTimeout(reTimeoutTimer)
  reTimeoutTimer = setTimeout(function () {
    createWebSocket()
    lockReconnect = false
  }, reTimeout)
}

2、基于websocket动态获取在线用户

想要实现实时获取在线用户列表,并且有用户连接上wesocket服务时,通知给其他在线用户。
服务器端

// 存储在线用户
let onlineUser = []
wss.on('connection', (ws, req, res) => {
// 获取当前连接websocket的用户uid
  let uid = req.url.substr(1)
  let index = onlineUser.findIndex(val => val.id === uid)
  if (index !== -1) {
    onlineUser[index].websocket = ws
  } else {
    onlineUser.push({
      websocket: ws,
      id: uid
    })

  }
  // 获取在线用户id数组
  let User = JSON.stringify({
    onlineUser: onlineUser.map(val => val.id),
    type: 'users'
  })
  // 通知所有在线用户有新用户上线
  onlineUser.forEach((client) => client.websocket.send(User))
})

用户端

// websocket收到信息的处理逻辑
    webSocket.onmessage = function (event) {
      if (event.data) {
        let res = JSON.parse(event.data)
        console.log(res)
        switch (res.type) {
          // 当消息为用户列表
          case 'users':
            userList.innerHTML = res.onlineUser.map(val => {
              if (val == uid) {
                return '<li>me:' + val + '</li>'
              } else {
                return '<li>' + val + '</li>'
              }
            }).join('')
            break
          default:
        }
      }
    }

3、基于ajax+websocket获取与某个用户的聊天信息

服务器端收到用户发送给另一个用户的消息后,遍历在线用户列表并匹配到接收消息的用户,将消息发送给所匹配到的用户。

ws.on('message', (msg) => {
    // 将收到的消息转成对象格式
    const buf = Buffer.from(msg)
    let notice = buf.toString('utf-8')
    let Notice = JSON.parse(notice)

    // 通知当前用户消息发送成功
    ws.send(JSON.stringify({
      msg: Notice.msg,
      type: 'chat'
    }))
    // 存储当前用户发送的数据
    chatInfo.push({
      from: uid,
      to: Notice.receiver,
      msg: Notice.msg
    })
    // 根据receiver匹配到接收消息的用户并发送通知
    onlineUser.forEach(function each(client) {
      if (client.id === Notice.receiver) {
        if (client.websocket.readyState === 1) {
          client.websocket.send(JSON.stringify({
            msg: Notice.msg,
            type: 'chat'
          }));
        }
      }
    });
  })

用户端,当接收到消息或者信息发送成功时,刷新聊天界面,重新请求聊天信息。

// websocket收到信息的处理逻辑
    webSocket.onmessage = function (event) {
      if (event.data) {
        let res = JSON.parse(event.data)
        console.log(res)
        switch (res.type) {
          // 当消息为用户列表
          case 'users':
            userList.innerHTML = res.onlineUser.map(val => {
              if (val == uid) {
                return '<li>me:' + val + '</li>'
              } else {
                return '<li>' + val + '</li>'
              }
            }).join('')
            break
          // 当消息为聊天信息,刷新聊天框
          case 'chat':
            if (chatBoxId) {
              reloadChatInfo()
            }
            break
          default:
        }
      }
    }
   // 根据当前聊天用户重新获取聊天信息
  function reloadChatInfo() {
    getDataMethod('get', 'http://localhost:8191/getchat', {
      friendId: chatBoxId,
      id: uid
    }).then(res => {
      let chatlist = JSON.parse(res)
      chatcontent.innerHTML = chatlist.map(val => {
        if (val.from == uid) {
          return '<p class="me">' + val.msg + ':me</p>'
        } else {
          return `<p class="other">${val.from}:${val.msg}</p>`
        }
      }).join('')
    })
  }
  // 获取聊天信息的ajax请求
  function getDataMethod(method, url, params) {
    if (params) {
      let arr = []
      for (let key in params) {
        arr.push(key + '=' + params[key])
      }
      url = url + '?' + arr.join('&')
    }
    return new Promise((resolve, reject) => {
      let xml = new XMLHttpRequest()
      xml.open(method, url, false)
      xml.onreadystatechange = function () {
          if (xml.readyState == 4){
            console.log(xml)
            if (xml.status == 200) {
              resolve(xml.response)
            } else {
              reject(new Error(xml.statusText))
            }
            
          }
      }
      xml.send()
    })
  }

Node.JS服务器代码


// 导入 express
const express = require('express')
// 创建服务器的实例对象
const app = express()

const WebSocket = require('ws')

// 存储聊天信息
let chatInfo = []
// 存储在线用户
let onlineUser = []
const wss = new WebSocket.Server({ port: 5001 })
// 建立websocket连接
wss.on('connection', (ws, req, res) => {
  console.log('server connection')
  // 获取当前连接websocket的用户uid
  let uid = req.url.substr(1)
  let index = onlineUser.findIndex(val => val.id === uid)
  if (index !== -1) {
    onlineUser[index].websocket = ws
  } else {
    onlineUser.push({
      websocket: ws,
      id: uid
    })

  }
  // 获取在线用户id数组
  let User = JSON.stringify({
    onlineUser: onlineUser.map(val => val.id),
    type: 'users'
  })
  // 通知所有在线用户有新用户上线
  onlineUser.forEach((client) => client.websocket.send(User))
  ws.on('message', (msg) => {
    // 将收到的消息转成对象格式
    const buf = Buffer.from(msg)
    let notice = buf.toString('utf-8')
    let Notice = JSON.parse(notice)

    // 通知当前用户消息发送成功
    ws.send(JSON.stringify({
      msg: Notice.msg,
      type: 'chat'
    }))
    // 存储当前用户发送的数据
    chatInfo.push({
      from: uid,
      to: Notice.receiver,
      msg: Notice.msg
    })
    // 根据receiver匹配到接收消息的用户并发送通知
    onlineUser.forEach(function each(client) {
      if (client.id === Notice.receiver) {
        if (client.websocket.readyState === 1) {
          client.websocket.send(JSON.stringify({
            msg: Notice.msg,
            type: 'chat'
          }));
        }
      }
    });
  })
  // 连接关闭后清除在线用户
  ws.on('close', res => {
    let newUserArr = []
    let newUserIds = []
    for (let i = 0; i < onlineUser.length; i++) {
      let val = onlineUser[i]
      if (val.id !== uid) {
        newUserArr.push(val)
        newUserIds.push(val.id)
      }
    }
    onlineUser = newUserArr
    let User = JSON.stringify({
      onlineUser: newUserIds,
      type: 'users'
    })
    onlineUser.forEach((client) => client.websocket.send(User))
  })

  ws.send(JSON.stringify({
    msg: '连接已建立',
    type: 'system'
  }))
})


//3.创建一个路由对象
let router = express.Router();

//4.设置路由
router.get('/getchat',(req,res)=>{
  let fromid = req.query.id
  let toid = req.query.friendId
  // 返回当前用户与消息接收用户的聊天记录
  let msgList = chatInfo.filter(val => (val.from === fromid && val.to === toid) || (val.from === toid && val.to === fromid))
  res.send(msgList);
})

// 配置跨域请求中间件
const cors = require('cors')
app.use(cors())
app.use(router);


app.listen(8191, () => {
  console.log('running')
})

html代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <!-- <meta http-equiv="X-UA-Compatible" content="IE=edge"> -->
  <meta http-equiv="Access-Control-Allow-Origin" content="*" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .content{
      display: flex;
    }
    .leftbox{
      width: 250px;
    }
    #rightbox{
      width: 500px;
      height: 500px;
      background-color: black;
      color: white;
    }
    #rightbox #chatcontent{
      width: 100%;
      height: 370px;
      overflow: auto;
    }
    #rightbox #inputchat {
      width: 100%;
      height: 100px;
      box-sizing: border-box;
    }
    #chatcontent .me{
      text-align: right;
    }
    #chatcontent .other{
      text-align: left;
    }
    #title{
      height: 30px;
    }
  </style>
</head>
<body>
  <button id="connectws">连接websocket</button>
  <div class="content">
    <div class="leftbox">
      <p>在线用户:</p>
      <ul id="userList">
      </ul>
    </div>
    <div id="rightbox">
      <div id="title"></div>
      <div id="chatcontent">

      </div>
      <input type="text" name="" id="inputchat">
    </div>
  </div>

</body>
<script>
  let lockReconnect = false // 是否允许重新连接
  let timeout = 1000 * 20 // 心跳间隔
  let reTimeout = 1000 * 10 // 重连间隔
  let timeoutTimer = null // 心跳定时器
  let reTimeoutTimer = null // 重连定时器
  let webSocket = null
  let uid = new Date().getTime()
  let chatBoxId = ''
  // 创建websocket连接
  function createWebSocket () {
    let 
    webSocket = new WebSocket('ws://localhost:5001/' + uid)
    webSocket.binaryType = 'arraybuffer';
    webSocket.onopen = function () {
      console.log('连接成功...')
      // 监听回车发送消息事件
      window.onkeypress = function(e) {
        if (e.keyCode === 13) {
          let msg = inputchat.value
          console.log(msg)
          if (chatBoxId && msg !== '') {
            webSocket.send(JSON.stringify({
              receiver: chatBoxId,
              msg: msg,
            }))
            inputchat.value = ''
          }
        }
      }
    }
    webSocket.onerror = function () {
      console.log('连接失败...')
      reConnect()
    }
    // websocket收到信息的处理逻辑
    webSocket.onmessage = function (event) {
      if (event.data) {
        let res = JSON.parse(event.data)
        console.log(res)
        switch (res.type) {
          // 当消息为用户列表
          case 'users':
            userList.innerHTML = res.onlineUser.map(val => {
              if (val == uid) {
                return '<li>me:' + val + '</li>'
              } else {
                return '<li>' + val + '</li>'
              }
            }).join('')
            break
          // 当消息为聊天信息
          case 'chat':
            if (chatBoxId) {
              reloadChatInfo()
            }
            break
          default:
        }
      }
    }
    webSocket.onclose = function (event) {
      console.log('连接已关闭...')
      console.log('websocket 断开: ' + event.code + ' ' + event.reason + ' ' + event.wasClean)
      reConnect()
    }
  }
  // 重连
  function reConnect () {
    if (lockReconnect) {
      return
    }
    lockReconnect = true
    // 没连接上会一直重连,设置延迟避免请求过多
    reTimeoutTimer && clearTimeout(reTimeoutTimer)
    reTimeoutTimer = setTimeout(function () {
      createWebSocket()
      lockReconnect = false
    }, reTimeout)
  }

  // 获取页面元素
  let btnws = document.getElementById('connectws')
  let title = document.getElementById('title')
  let inputchat = document.getElementById('inputchat')
  let chatcontent = document.getElementById('chatcontent')
  let userList = document.getElementById('userList')


  btnws.addEventListener('click', () => {
    createWebSocket()
  })
  // 监听在线用户点击事件
  userList.addEventListener('click', (e) => {
    if (!e.target.innerText.includes('me')) {
      chatBoxId = e.target.innerText
      title.innerHTML = '与' + chatBoxId + '的聊天'
      reloadChatInfo()
    }
  })
  // 根据当前聊天用户重新获取聊天信息
  function reloadChatInfo() {
    getDataMethod('get', 'http://localhost:8191/getchat', {
      friendId: chatBoxId,
      id: uid
    }).then(res => {
      let chatlist = JSON.parse(res)
      chatcontent.innerHTML = chatlist.map(val => {
        if (val.from == uid) {
          return '<p class="me">' + val.msg + ':me</p>'
        } else {
          return `<p class="other">${val.from}:${val.msg}</p>`
        }
      }).join('')
    })
  }
  // 获取聊天信息的ajax请求
  function getDataMethod(method, url, params) {
    if (params) {
      let arr = []
      for (let key in params) {
        arr.push(key + '=' + params[key])
      }
      url = url + '?' + arr.join('&')
    }
    return new Promise((resolve, reject) => {
      let xml = new XMLHttpRequest()
      xml.open(method, url, false)
      xml.onreadystatechange = function () {
          if (xml.readyState == 4){
            console.log(xml)
            if (xml.status == 200) {
              resolve(xml.response)
            } else {
              reject(new Error(xml.statusText))
            }
            
          }
      }
      xml.send()
    })
  }
  
</script>
</html>
  • 0
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值