# 1、ws 库

安装

npm i ws

官方文档

https://github.com/websockets/ws

使用

  • http 协议

访问 http://127.0.0.1:3000 即可

const express = require('express')
const app = express()

// 访问静态页面
app.use(express.static('./public'))
// 响应数据
app.get('/', (req, res) => {
  res.send({ ok: 1 })
})

app.listen(3000)
  • Websocket 协议
<!-- 一、前端 -->
<body>
  <div>聊天室</div>
  <script>
    var ws = new WebSocket('ws://127.0.0.1:8080?token=&(localStorage.getItem("token"))')

    ws.onopen = () => {
      console.log('连接成功')
    }

    // 接收数据✨
    ws.onmessage = (msgObj) => {
      console.log('服务端发送的数据:', msgObj.data)
    }

    ws.onerror = () => {
      console.log('error')
    }
  </script>
</body>
# 浏览器控制台
ws

# 客户端发送数据👀
ws.send('获取聊天室实时数据')
// 二、后端
const WebSocket = require('ws')
const WebSocketServer = WebSocket.WebSocketServer

const wss = new WebSocketServer({ port: 8080 })

wss.on('connection', function connection(ws, req) {
  // token验证
  const myURL = new URL(req.url, 'http://127.0.0.1:3000')
  const payload = JWT.verify(myURL.searchParams.get('token'))

  // 接收数据👀
  ws.on('message', function message(data) {
    // ① 普通方式
    console.log('客户端发送的数据:%s', data)
    // ② 群发方式🚩(可用多标签页或客户端模拟)
    wss.clients.forEach(function each(client) {
      // 不包括自己(若要包括自己:删除client !== ws条件即可)
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(data, { binary: false })
      }
    })
  })

  // 服务端发送数据✨
  if (payload) {
    ws.send('欢迎来到聊天室!')
  } else {
    ws.send('未授权')
  }
})
  • ws.send 优化
<!-- 一、前端 -->
<body>
  <div>聊天室</div>
  <script>
    // 接收数据✨
    ws.onmessage = (msgObj) => {
      msgObj = JSON.parse(msgObj.data)

      switch (msgObj.type) {
        case 0:
          localStorage.removeItem('token')
          location.href = '/login'
          break

        case 2:
          console.log(msgObj)
          break
      }
    }

    ws.onerror = () => {
      console.log('error')
    }
  </script>
</body>
// 二、后端
const WebSocketType = {
  Error: 0, // 错误
  GroupList: 1, // 获取列表
  GroupChat: 2, // 群聊
  SingleChat: 3 // 私聊
}

function createMsg(type, user, data) {
  return JSON.stringify({
    type,
    user,
    data
  })
}

wss.on('connection', function connection(ws, req) {
  // 服务端发送数据✨
  if (payload) {
    ws.send(createMsg(WebSocketType.GroupChat, null, '欢迎来到聊天室!'))
  } else {
    ws.send(createMsg(WebSocketType.Error, null, 'token过期'))
  }
})

  上面大概描述了 WebSocket 的使用情况,下面我们将要完整的构造一个聊天室。

  • 前端
<!-- 一、前端 -->
<body>
  <div>聊天室</div>
  <script>
    // 接收数据✨
    ws.onmessage = (msgObj) => {
      msgObj = JSON.parse(msgObj.data)

      switch (msgObj.type) {
        case 0:
          localStorage.removeItem('token')
          location.href = '/login'
          break

        case 1:
          // 打印在线用户信息
          console.log(JSON.parse(msgObj.data))

          const onelineList = JSON.parse(msgObj.data)

          // 🎈 在线用户列表渲染
          spanCycle.innerHTML = ''
          spanCycle.innerHTML = onelineList.map(
            (item) => `
            <span>${item.username}</span>
          `
          )
          break

        case 2:
          // 🎈 打印在线群聊信息
          var chatTitle = msgObj.user ? msgObj.user.username : '公告'
          console.log(chatTitle + ':' + msgObj.data)
          break

        case 3:
          // 🎈 打印在线私聊信息
          console.log(msgObj.user.username + ':' + msgObj.data)
          break
      }
    }

    ws.onerror = () => {
      console.log('error')
    }

    // 编写🚩聊天信息
    sendButton.onclick = () => {
      if (selectOption.value === 'all') {
        // ① 群发
        ws.send(JSON.stringify({ type: 2, data: inputTextID.value }))
      } else {
        // ② 私发
        ws.send(
          JSON.stringify({
            type: 3,
            data: inputTextID.value,
            to: selectOption.value
          })
        )
      }
    }
  </script>
</body>
# 浏览器控制台
ws

# 客户端发送数据👀
ws.send(JSON.stringify({type: 1}))
  • 后端
// 二、后端
const WebSocket = require('ws')
const WebSocketServer = WebSocket.WebSocketServer

const wss = new WebSocketServer({ port: 8080 })

// 获取在线🚩用户信息封装
function sendUserList() {
  const userList = Array.from(wss.clients).map((item) => item.user)

  ws.send(createMsg(WebSocketType.GroupList, null, userList))
}

wss.on('connection', function connection(ws, req) {
  // token验证
  const myURL = new URL(req.url, 'http://127.0.0.1:3000')
  const payload = JWT.verify(myURL.searchParams.get('token'))

  // 发送数据
  if (payload) {
    ws.send(createMsg(WebSocketType.GroupChat, null, '欢迎来到聊天室!'))

    // 1、为ws对象添加user属性(存储🤔用户信息)
    ws.user = payload

    // 2.2、发送在线用户信息【登录状态时】(群发方式,包括自己)
    wss.clients.forEach(function each(client) {
      if (client.readyState === WebSocket.OPEN) {
        sendUserList()
      }
    })
  } else {
    ws.send(createMsg(WebSocketType.Error, null, 'token过期'))
  }

  // 接收数据
  ws.on('message', function message(data) {
    const msgObj = JSON.parse(data)

    switch (msgObj.type) {
      case WebSocketType.GroupList:
        // 2.1、发送在线用户信息【前端请求时】(wss.clients结果为set🤙结构)
        sendUserList()
        break

      case WebSocketType.GroupChat:
        // ① 群发 —— 群聊信息
        // console.log(msgObj.data)

        wss.clients.forEach(function each(client) {
          if (client.readyState === WebSocket.OPEN) {
            client.send(createMsg(WebSocketType.GroupChat, ws.user, msgObj.data), { binary: false })
          }
        })
        break

      case WebSocketType.SingleChat:
        // ② 私发 —— 群聊信息
        // console.log(msgObj.data)

        wss.clients.forEach(function each(client) {
          if (client.user.username === msgObj.to && client.readyState === WebSocket.OPEN) {
            client.send(createMsg(WebSocketType.SingleChat, ws.user, msgObj.data), {
              binary: false
            })
          }
        })
        break
    }
  })

  ws.on('close', () => {
    // set结构删除🤙操作(删除离线用户)
    wss.clients.delete(ws.user)

    // 2.3、发送在线用户信息【退出登录时】(实时更新)
    wss.clients.forEach(function each(client) {
      if (client.readyState === WebSocket.OPEN) {
        sendUserList()
      }
    })
  })
})

# 2、socket.io

安装

npm i socket.io

官方文档

https://github.com/socketio/socket.io

① 对比

  前端的 socket.on() 和 后端的 socket.emit() 类似于 vue 中的数据共享。并且还有特性:

  • socket.emit 是支持发送对象,无需转换为字符串
  • ws 复杂的 case 方式,替换为了简洁的 socket.on 方式
  • 能够自动的降级处理
  • 能够断连时自动重连
  • ……

② 聊天室案例改造(express 中使用)

  • 前端
<!-- 一、前端 -->
<head>
  <!-- 引入socket.io的客户端代码 -->
  <script src="https://github.com/socketio/socket.io/tree/main/client-dist/socket.io.min.js"></script>
</head>
<body>
  <div>聊天室</div>
  <script>
    const socket = io('ws://127.0.0.1:8080?token=&(localStorage.getItem("token"))')

    // 打印公告
    socket.on(2, (msgObj) => {
      var chatTitle = msgObj.user ? msgObj.user.user : '公告'
      console.log(chatTitle + ':' + msgObj.data)
    })

    // 打印错误
    socket.on(0, (msgObj) => {
      localStorage.removeItem('token')
      location.href = '/login'
    })

    // 打印在线用户信息
    socket.on(1, (msgObj) => {
      const onelineList = msgObj.data

      // 🎈 在线用户列表渲染
      spanCycle.innerHTML = ''
      spanCycle.innerHTML = onelineList.map(
        (item) => `
            <span>${item.username}</span>
          `
      )
    })

    socket.on(2, (msgObj) => {
      // 🎈 打印在线群聊信息
      var chatTitle = msgObj.user ? msgObj.user.username : '公告'
      console.log(chatTitle + ':' + msgObj.data)
    })

    socket.on(3, (msgObj) => {
      // 🎈 打印在线私聊信息
      console.log(msgObj.user.username + ':' + msgObj.data)
    })

    // 编写🚩聊天信息
    sendButton.onclick = () => {
      if (selectOption.value === 'all') {
        // ① 群发
        socket.emit(2, { data: inputTextID.value })
      } else {
        // ② 私发
        socket.emit(3, {
          data: inputTextID.value,
          to: selectOption.value
        })
      }
    }
  </script>
</body>
# 浏览器控制台
io

# 客户端发送数据👀
io.emit({type: 1})
  • 后端
// 二、后端
const app = require('express')()
const server = require('http').createServer(app)

const io = require('socket.io')(server)

// 获取在线🚩用户信息封装
function emitUserList(io) {
  // const userList = Array.from(is.sockets.sockets).map((item) => item[1].user)
  // undefined优化
  const userList = Array.from(is.sockets.sockets)
    .map((item) => item[1].user)
    .filter((item) => item)

  io.sockets.emit(WebSocketType.GroupList, createMsg(null, userList))
}

io.on('connection', (socket, req) => {
  // token验证
  // 发现已经帮我们解析👏
  // console.log(socket.handshake.query.token)

  const payload = JWT.verify(socket.handshake.query.token)

  // 发送数据
  if (payload) {
    socket.emit(WebSocketType.GroupChat, createMsg(null, '欢迎来到聊天室!'))

    // 1、为ws对象添加user属性(存储🤔用户信息)
    socket.user = payload

    // 2.2、发送在线用户信息【登录状态时】(群发方式,io.sockets.emit)
    emitUserList(io)
  } else {
    socket.emit(WebSocketType.Error, createMsg(null, 'token过期'))
  }

  socket.on(WebSocketType.GroupList, () => {
    // console.log(is.sockets.sockets)

    // 2.1、发送在线用户信息【前端请求时】(is.sockets.sockets结果为map🤙结构)
    emitUserList(io)
  })

  // ① 群发 —— 群聊信息
  socket.on(WebSocketType.GroupChat, (msg) => {
    // console.log(msg)

    // 包含自己(io.sockets.emit)
    io.sockets.emit(WebSocketType.GroupChat, createMsg(socket.user, msg.data))

    // 不包含自己(sockets.broadcast.emit)
  })

  // ② 私发 —— 群聊信息
  socket.on(WebSocketType.GroupList, (msgObj) => {
    Array.from(is.sockets.sockets).forEach((item) => {
      if (item[1].user.username === msgObj.to) {
        // 私发🍗
        item[1].emit(WebSocketType.GroupChat, createMsg(socket.user, msgObj.data))
      }
    })
  })

  socket.on('close', () => {
    // 2.3、发送在线用户信息【退出登录时】(实时更新)
    emitUserList(io)
  })
})
server.listen(3000)
更新于 : 8/7/2024, 2:16:31 PM