Websocket

websocket

支持双向通信

使用场景:

  • 大屏展示
  • 日志监控
  • 即时聊天
  • 通知系统

语法

创建

1
const ws = new WebSocket(url[, protocols])
1
2
3
const uid = 123
const url = 'localhost:3000'
const ws = new WebSocket(`ws://${url}/ws/${uid}`)

属性

  • readyState: 链接状态
    • 0: 正在尝试建立连接
    • 1: 已链接, 可以通讯
    • 2: 正在关闭
    • 3: 已关闭或无法打开
  • url: 绝对路径

事件

通过 addEventListener() 监听

  • open
  • message
  • error
  • close
1
2
3
4
ws.addEventListener('open', handleOpen)
ws.addEventListener('message', handleMessage)
ws.addEventListener('error', handleError)
ws.addEventListener('close', handleClose)

方法

  • send
  • close
1
2
3
ws.send('hello')

ws.close()

示例

前端

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div class="status">
      <span>连接状态:</span>
      <span id="status">未连接</span>
    </div>
    <div class="close">
      <button id="close">关闭连接</button>
    </div>
    <div class="reconnect">
      <button id="reconnect">重新连接</button>
    </div>
    <div>
      <input type="text" id="message" />
      <button id="send">发送</button>
    </div>
    <div class="messages">
      <ul id="messages"></ul>
    </div>
    <script>
      const uid = 123
      const url = 'localhost:3003'
      let ws = null // 添加全局 WebSocket 变量
      let isCloseByHand = false // 是否手动关闭

      // 定义事件处理函数
      const handleOpen = e => {
        console.log(e)
        console.log('连接成功')
        document.getElementById('status').innerText = '已连接'
        console.log('readyState:', ws.readyState)
      }

      const handleMessage = e => {
        console.log(e)
        console.log('收到消息')
        const messages = document.getElementById('messages')
        messages.innerHTML += `<li>${e.data}</li>`
      }

      const handleError = e => {
        console.log(e)
        console.log('连接失败')
        document.getElementById('status').innerText = '连接失败'
      }

      const handleClose = e => {
        console.log(e)
        console.log('连接关闭')
        document.getElementById('status').innerText = '连接关闭'
        // 自动重连
        console.log('isCloseByHand:',isCloseByHand)
        if (!isCloseByHand) {
          let retryFn = handleRetry()
          retryFn()
          document.getElementById('status').innerText = '正在重连'
        }
      }

      // 自动重连
      const handleRetry = () => {
        console.log('进入重连')
        let timeId = null
        let count = 0
        let maxRestry = 10
        let fn = () => {
          timeId = setInterval(() => {
            if (ws.readyState == 1 || count >= maxRestry) {
              console.log('检测到连接成功或重试次数大于10,断开定时器')
              clearInterval(timeId)
              timeId = null
              return
            }
            console.log('正在重连...')
            wsInit()
            count++
          }, 10*1000)
        }
        return fn
      }

      const wsInit = () => {
        // 如果已存在连接,先关闭它
        if (ws) {
          ws.close()
          // 移除所有旧的事件监听器
          ws.removeEventListener('open', handleOpen)
          ws.removeEventListener('message', handleMessage)
          ws.removeEventListener('error', handleError)
          ws.removeEventListener('close', handleClose)
        }
        // 创建新的 WebSocket 连接
        ws = new WebSocket(`ws://${url}/ws/${uid}`)

        isCloseByHand = false

        if (ws.readyState !== 0) return

        // 添加事件监听器
        ws.addEventListener('open', handleOpen)
        ws.addEventListener('message', handleMessage)
        ws.addEventListener('error', handleError)
        ws.addEventListener('close', handleClose)
      }

      // 将 DOM 事件监听器移到外面,只绑定一次
      document.getElementById('send').addEventListener('click', () => {
        if (ws && ws.readyState === 1) {
          const message = document.getElementById('message').value
          ws.send(message)
        } else {
          console.log('WebSocket 未连接')
        }
      })

      document.getElementById('close').addEventListener('click', () => {
        if (ws) {
          ws.close()
          isCloseByHand = true
        }
      })

      document.getElementById('reconnect').addEventListener('click', () => {
        wsInit()
      })

      // 初始化连接
      wsInit()
    </script>
  </body>
</html>

后端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
'use strict'

const fastify = require('fastify')()

// 注册 WebSocket 插件
fastify.register(require('@fastify/websocket'), {
  options: { maxPayload: 1048576 },
})

// 添加连接映射表
const connections = new Map()

// 注册 WebSocket 路由
fastify.register(async function (fastify) {
  fastify.get('/ws/:uid', { websocket: true }, (socket, req) => {
    const userId = req.params.uid
    console.log(`用户 ${userId} 已连接`)

    // 存储连接
    connections.set(userId, socket)

    socket.send(`欢迎用户 ${userId}`)

    socket.on('message', message => {
      console.log(`收到来自用户 ${userId} 的消息:`, message.toString())
      socket.send(`服务器已收到消息: ${message}`)
    })

    socket.on('close', () => {
      console.log(`用户 ${userId} 断开连接`)
      connections.delete(userId)
      socket.close()
    })
  })
  fastify.get('/send', (req, reply) => {
    const { msg, uid } = req.query
    if (!msg || !uid) {
      return reply.code(400).send({
        success: false,
        message: '缺少必要参数,请确保提供 msg 和 uid',
        example: '/send?msg=你的消息&uid=用户ID',
      })
    }
    console.log(`尝试发送消息给用户 ${uid}: ${msg}`)

    const userSocket = connections.get(uid)
    if (userSocket && userSocket.readyState === 1) {
      userSocket.send(msg)
      reply.send({ success: true, message: `消息已发送给用户 ${uid}: ${msg}` })
    } else {
      reply.code(404).send({
        success: false,
        message: `用户 ${uid} 不在线或连接已断开`,
      })
    }
  })
})

// 直接在外部注册 `send` 路由

fastify.listen({ port: 3003, host: '0.0.0.0' }, err => {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  console.log('WebSocket 服务器运行在端口 3003')
})

fastify.ready(err => {
  // 打印所有路由
  if (err) throw err
  console.log(fastify.printRoutes())
})
build with Hugo, theme Stack, visits 0