# 一、简介

# 1、快速认知

图解(HTTP 与 websocket):

学习文档:

阮一峰的 WebSocket 教程 (opens new window)

# 2、应用场景

  • 弹幕
  • 协同编辑
  • 外卖员位置
  • 数据实时更新(体育赛况、股票报价等等)

# 二、全双工通信

# 1、TCP 协议

  Websocket 并不是全新的协议(2008 年就诞生了),它是利用了 HTTP 协议来建立连接的(或者说是建立在 TCP 协议之上的)。

TCP 协议本身就实现了全双工通信

# 2、安全通信

  安全的 WebSocket 连接机制和 HTTPS 类似,底层通信走的仍然是安全的 SSL/TLS 协议。

# 三、实时通信

# 1、HTTP 协议

  HTTP 协议做不到服务器主动向客户端推送信息,通信只能由客户端发起(单向请求)

提示

HTTP 协议是无状态的,每个请求都是独立的,客户端向服务器发送请求后,服务器返回响应后就会关闭连接,这种方式称为“请求-响应模式”

实际上 HTTP 协议是建立在 TCP 协议上的,但是 HTTP 协议的请求-应答机制限制了全双工通信

  但是,我们任然可以通过"轮询"方式实现实时通信。

轮询
  • 前端
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>实时通信</title>
  </head>
  <body>
    <h1>实时通信</h1>
    <form>
      <input id="messageInput" />
      <button id="sendButton">发送</button>
    </form>
    <ul id="messages"></ul>
    <script>
      const messageList = document.getElementById('messages')
      const messageInput = document.getElementById('messageInput')
      const sendButton = document.getElementById('sendButton')

      // 发送消息到服务器
      function sendMessage(message) {
        fetch('http://localhost:3000/messages', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ message })
        })
      }

      // 获取最新的消息
      function getMessages() {
        fetch('http://localhost:3000/messages')
          .then((res) => res.json())
          .then((messages) => {
            messageList.innerHTML = ''
            messages.forEach((message) => {
              const li = document.createElement('li')
              li.textContent = message
              messageList.appendChild(li)
            })
            // 获取完最新的消息后再次启动定时器
            setTimeout(getMessages, 1000)
          })
      }

      // 发送消息按钮点击事件
      sendButton.addEventListener('click', (event) => {
        event.preventDefault()
        const message = messageInput.value.trim()
        console.log(message)
        if (message) {
          sendMessage(message)
          messageInput.value = ''
        }
      })

      // 启动定时器
      setTimeout(getMessages, 1000)
    </script>
  </body>
</html>
  • 后端
const express = require('express')
const app = express()
const cors = require('cors')
app.use(express.urlencoded({ extended: false })) // JSON数据处理👀
app.use(express.json())
app.use(cors())

// 定义一个数组,用于保存所有的消息
let messages = []

// 定义一个路由,用于获取所有的消息
app.get('/messages', (req, res) => {
  res.send(messages)
})

// 定义一个路由,用于添加一条新的消息
app.post('/messages', (req, res) => {
  const message = req.body.message
  if (message) {
    messages.push(message)
    console.log(messages)
  }
  res.sendStatus(200)
})

// 监听端口
app.listen(3000, () => {
  console.log('App listening on port 3000!')
})

# 2、WebSocket 协议

  websocket 协议可以在客户端和服务器之间创建一个持久连接,使得双方能够随时向对方发送数据。

特性

  • 可以发送文本,也可以发送二进制数据
  • 没有同源限制
  • 协议标识符是 ws(如果加密,则为 wss),服务器网址就是 URL

Node.js 本身以支持的协议包是包含 TCP 协议和 HTTP 协议的,因此要支持 WebSocket 协议的话,只需要添加并配置一下模块即可。

  websocket 是"长连接服务器端推技术"的一种。

# ① ws 库

使用 ws 是基于原生的 WebSocket API (opens new window) 实现的,不会被同源策略所限制

ws 库
  • 前端
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>实时通信</title>
  </head>
  <body>
    <h1>实时通信</h1>
    <form>
      <input id="messageInput" />
      <button id="sendButton">发送</button>
    </form>
    <ul id="messages"></ul>
    <script>
      const messageList = document.getElementById('messages')
      const messageInput = document.getElementById('messageInput')
      const sendButton = document.getElementById('sendButton')
      let socket = null

      // 连接 WebSocket 服务器
      function connect() {
        socket = new WebSocket('ws://localhost:3000')

        // 监听 WebSocket 连接
        socket.onopen = () => {
          console.log('WebSocket 连接已建立')
        }

        // 监听 WebSocket 消息
        socket.onmessage = (event) => {
          const message = event.data
          const li = document.createElement('li')
          li.textContent = message
          messageList.appendChild(li)
        }

        // 监听 WebSocket 关闭事件
        socket.onclose = (event) => {
          console.log('WebSocket 连接已关闭')
          setTimeout(connect, 3000)
        }
      }

      // 发送消息到 WebSocket 服务器
      function sendMessage(message) {
        socket.send(message)
      }

      // 发送消息按钮点击事件
      sendButton.addEventListener('click', (event) => {
        event.preventDefault()
        const message = messageInput.value.trim()
        if (message) {
          sendMessage(message)
          messageInput.value = ''
        }
      })

      // 连接 WebSocket 服务器
      connect()
    </script>
  </body>
</html>
  • 后端
const express = require('express')
const http = require('http')
const WebSocket = require('ws')

// 创建 HTTP 服务器并将其传递给 WebSocket 服务器
const server = http.createServer(express())
const wss = new WebSocket.Server({ server })

// 储存所有客户端的连接
const clients = new Set()

// 监听 WebSocket 连接
wss.on('connection', (ws) => {
  // 将新的连接加入到客户端集合中
  clients.add(ws)

  // 监听 WebSocket 消息
  ws.on('message', (message) => {
    // 将消息广播给所有客户端
    clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(message, { binary: false }) // 不要使用二进制👀
      }
    })
  })

  // 监听 WebSocket 关闭事件
  ws.on('close', () => {
    // 将断开连接的客户端从集合中移除
    clients.delete(ws)
  })
})

// 启动服务器(注意这里所有server监听👀)
server.listen(3000, () => {
  console.log('服务器已启动')
})

# ② socket.io 库

引入 socket.io 的client-dist (opens new window)文件

socket.io
  • 前端
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>实时通信</title>
    <script src="./socket.io.min.js"></script>
  </head>
  <body>
    <h1>实时通信</h1>
    <form>
      <input id="messageInput" />
      <button id="sendButton">发送</button>
    </form>
    <ul id="messages"></ul>
    <script>
      const messageList = document.getElementById('messages')
      const messageInput = document.getElementById('messageInput')
      const sendButton = document.getElementById('sendButton')

      // socket = new WebSocket('ws://localhost:3000')
      const socket = io('http://localhost:3000', { transport: ['websocket'] }) // 指定传输方式

      // 监听 WebSocket 连接 ✖
      // socket.onopen = () => {
      //   console.log('WebSocket 连接已建立')
      // }

      // 监听 WebSocket 消息
      // socket.onmessage = event => {
      //   const message = event.data
      //   const li = document.createElement('li')
      //   li.textContent = message
      //   messageList.appendChild(li)
      // }
      // 👀
      socket.on('message', (message) => {
        const li = document.createElement('li')
        li.textContent = message
        messageList.appendChild(li)
      })

      // 监听 WebSocket 关闭事件 ✖
      // socket.onclose = event => {
      //   console.log('WebSocket 连接已关闭')
      //   setTimeout(connect, 3000)
      // }

      // 发送消息到 WebSocket 服务器
      function sendMessage(message) {
        // socket.send(message)
        socket.emit('message', message) // 👀
      }

      // 发送消息按钮点击事件
      sendButton.addEventListener('click', (event) => {
        event.preventDefault()
        const message = messageInput.value.trim()
        if (message) {
          // sendMessage(message)
          socket.emit('message', message) // 👀
          messageInput.value = ''
        }
      })
    </script>
  </body>
</html>
  • 后端
const express = require('express')
const http = require('http')
const socketIO = require('socket.io')

// // 创建 HTTP 服务器并将其传递给 WebSocket 服务器
const server = http.createServer(express())
const io = socketIO(server, {
  cors: {
    origin: 'http://localhost:8081' // 跨域问题(使用cors包行不通)
  }
})

// 监听 WebSocket 连接
io.on('connection', (socket) => {
  // 自动处理(add)👀

  // 监听 WebSocket 消息
  socket.on('message', (msg) => {
    // 广播消息给所有连接的客户端
    io.emit('message', msg) // 简化🤔
  })

  // 监听断开连接事件
  socket.on('disconnect', () => {
    // 自动处理(delete)👀
  })
})

// 启动服务器
server.listen(3000, () => {
  console.log('服务器已启动')
})

# 3、websocket 库

  WebSocket 是一种基于 TCP 协议进行全双工通信的协议,通常用于实时的数据交换。在 Node.js 中,有许多流行的 npm 包可以用于 WebSocket 通信(服务器端推技术)。以下是一些常用的 npm 包:

  • ws (opens new window):只支持原生的 WebSocket 协议,并且 API 接口比较简单,只提供了基本的 WebSocket 功能
  • socket.io (opens new window):支持多种传输协议(WebSocket、HTTP 长轮询、HTTP 短轮询等),还提供了自动重连等功能

# 四、聊天室

  实际上在线聊天室还需要实现很多其他功能,比如用户身份验证、聊天消息的持久化存储、实时通知等。

更新于 : 8/7/2024, 2:16:31 PM