> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# WebSocket 完全指南
- URL: https://blog.vercanti.com/websocket-wan-quan-zhi-nan/
- Published: 2026-08-28T14:35:28.000Z
- Updated: 2026-08-28T14:58:53.000Z
- Description: WebSocket 是一种在单个 TCP 连接上提供全双工通信的协议，适用于需要服务端主动推送数据的场景（实时聊天、行情推送、协作编辑等）。 MessageEvent 字段： CloseEvent 字段： VueUse 提供了开箱即用的 useWebSocket，支持自动重连和心跳： VueUse useWebSocket 主要参数： 约定 type + payload 结构，方便在客户端进行消息路由： 使用 ArrayBuffer + DataView 构建紧凑的二进制帧： 在发送消息前始终检查 readyState，避免向未就绪的连接发送消息： 为每
- Author: yellowdog
- Tags: 前端开发

> 官方文档：<https://developer.mozilla.org/zh-CN/docs/Web/API/WebSockets%5FAPI>  
> 适用版本：WebSocket API（Living Standard，2026-05-07 核实）

WebSocket 是一种在单个 TCP 连接上提供全双工通信的协议，适用于需要服务端主动推送数据的场景（实时聊天、行情推送、协作编辑等）。

---

## 原生 WebSocket API

### 构造函数参数

| 参数        | 类型                   | 必填 | 说明                                    |
| --------- | -------------------- | -- | ------------------------------------- |
| url       | string               | 是  | WebSocket 服务地址，使用 ws:// 或 wss://（TLS） |
| protocols | string \| string\[\] | 否  | 子协议，服务端从中选择一个，可通过 ws.protocol 查看协商结果  |

```typescript
// 基础连接
const ws = new WebSocket('ws://localhost:8000/ws')

// 指定子协议
const ws = new WebSocket('wss://api.example.com/ws', ['chat', 'v2'])

```

### readyState 枚举值

| 值 | 常量                   | 说明           |
| - | -------------------- | ------------ |
| 0 | WebSocket.CONNECTING | 正在建立连接       |
| 1 | WebSocket.OPEN       | 连接已建立，可以收发数据 |
| 2 | WebSocket.CLOSING    | 连接正在关闭       |
| 3 | WebSocket.CLOSED     | 连接已关闭或无法建立   |

### 事件

| 事件        | 触发时机   | 事件对象类型       |
| --------- | ------ | ------------ |
| onopen    | 连接建立成功 | Event        |
| onmessage | 收到消息   | MessageEvent |
| onerror   | 发生错误   | Event        |
| onclose   | 连接关闭   | CloseEvent   |

`MessageEvent` 字段：

| 字段          | 类型                    | 说明        |      |
| ----------- | --------------------- | --------- | ---- |
| data        | string \| ArrayBuffer | Blob      | 消息内容 |
| origin      | string                | 消息来源 URL  |      |
| lastEventId | string                | 最后一个事件 ID |      |

`CloseEvent` 字段：

| 字段       | 类型      | 说明       |
| -------- | ------- | -------- |
| code     | number  | 关闭状态码    |
| reason   | string  | 关闭原因文字说明 |
| wasClean | boolean | 是否正常关闭   |

```typescript
const ws = new WebSocket('ws://localhost:8000/ws')

ws.onopen = (event) => {
  console.log('连接已建立')
  ws.send(JSON.stringify({ type: 'hello' }))
}

ws.onmessage = (event) => {
  const data = JSON.parse(event.data as string)
  console.log('收到消息:', data)
}

ws.onerror = (event) => {
  console.error('WebSocket 错误:', event)
}

ws.onclose = (event) => {
  console.log(`连接关闭，code: ${event.code}, reason: ${event.reason}`)
}

```

### send 支持的数据类型

| 类型              | 说明                          |
| --------------- | --------------------------- |
| string          | 文本消息，最常用，通常传 JSON 字符串       |
| ArrayBuffer     | 二进制数据，适合传输结构化二进制            |
| Blob            | 二进制大对象，适合传输文件               |
| ArrayBufferView | 如 Uint8Array，typed array 视图 |

```typescript
// 文本消息
ws.send(JSON.stringify({ type: 'chat', content: 'Hello' }))

// 二进制消息
const buffer = new ArrayBuffer(4)
const view = new DataView(buffer)
view.setUint32(0, 12345)
ws.send(buffer)

```

### close 关闭码

| 代码   | 含义                        |
| ---- | ------------------------- |
| 1000 | 正常关闭，会话已完成                |
| 1001 | 端点离开（如页面关闭、服务器关闭）         |
| 1002 | 协议错误                      |
| 1003 | 收到无法接受的数据类型               |
| 1005 | 未提供关闭状态码（保留，不可用于 close()） |
| 1006 | 连接异常断开（保留，不可用于 close()）   |
| 1007 | 收到的数据与消息类型不一致             |
| 1008 | 收到违反策略的消息                 |
| 1009 | 消息过大                      |
| 1010 | 服务端未完成扩展协商                |
| 1011 | 服务端发生意外错误                 |
| 1015 | TLS 握手失败（保留，不可用于 close()） |

```typescript
// 正常关闭
ws.close(1000, 'Normal closure')

// 应用层主动断开
ws.close(1001, 'Going away')

```

---

## 封装实践

### 完整封装（自动重连 + 心跳 + 消息队列）

```typescript
interface WebSocketOptions {
  url: string
  // 最大重连次数，默认 5
  maxRetries?: number
  // 初始重连延迟（毫秒），指数退避基础值，默认 1000
  retryDelay?: number
  // 心跳间隔（毫秒），默认 30000，设为 0 禁用
  heartbeatInterval?: number
  // 心跳消息内容
  heartbeatMessage?: string
  // 消息接收回调
  onMessage?: (data: unknown) => void
  onOpen?: () => void
  onClose?: (event: CloseEvent) => void
  onError?: (event: Event) => void
}

class ReconnectingWebSocket {
  private ws: WebSocket | null = null
  private retryCount = 0
  private heartbeatTimer: ReturnType<typeof setInterval> | null = null
  private messageQueue: string[] = []
  private destroyed = false

  private readonly options: Required<WebSocketOptions>

  constructor(options: WebSocketOptions) {
    this.options = {
      maxRetries: 5,
      retryDelay: 1000,
      heartbeatInterval: 30000,
      heartbeatMessage: JSON.stringify({ type: 'ping' }),
      onMessage: () => {},
      onOpen: () => {},
      onClose: () => {},
      onError: () => {},
      ...options
    }
    this.connect()
  }

  private connect() {
    if (this.destroyed) return

    this.ws = new WebSocket(this.options.url)

    this.ws.onopen = () => {
      this.retryCount = 0
      this.startHeartbeat()
      // 连接就绪后发送队列中缓存的消息
      this.flushQueue()
      this.options.onOpen()
    }

    this.ws.onmessage = (event) => {
      // 过滤心跳 pong 响应
      try {
        const data = JSON.parse(event.data as string)
        if (data.type === 'pong') return
        this.options.onMessage(data)
      } catch {
        this.options.onMessage(event.data)
      }
    }

    this.ws.onerror = (event) => {
      this.options.onError(event)
    }

    this.ws.onclose = (event) => {
      this.stopHeartbeat()
      this.options.onClose(event)

      // 非正常关闭时自动重连
      if (!this.destroyed && event.code !== 1000) {
        this.scheduleReconnect()
      }
    }
  }

  private scheduleReconnect() {
    if (this.retryCount >= this.options.maxRetries) {
      console.error('WebSocket 重连次数已达上限，停止重连')
      return
    }

    // 指数退避：1s, 2s, 4s, 8s, 16s...
    const delay = this.options.retryDelay * Math.pow(2, this.retryCount)
    this.retryCount++
    console.log(`${delay}ms 后进行第 ${this.retryCount} 次重连...`)
    setTimeout(() => this.connect(), delay)
  }

  private startHeartbeat() {
    if (this.options.heartbeatInterval <= 0) return
    this.heartbeatTimer = setInterval(() => {
      if (this.ws?.readyState === WebSocket.OPEN) {
        this.ws.send(this.options.heartbeatMessage)
      }
    }, this.options.heartbeatInterval)
  }

  private stopHeartbeat() {
    if (this.heartbeatTimer !== null) {
      clearInterval(this.heartbeatTimer)
      this.heartbeatTimer = null
    }
  }

  private flushQueue() {
    while (this.messageQueue.length > 0) {
      const msg = this.messageQueue.shift()!
      this.ws?.send(msg)
    }
  }

  send(data: unknown) {
    const message = typeof data === 'string' ? data : JSON.stringify(data)

    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(message)
    } else {
      // 连接未就绪时缓存消息，等连接建立后发送
      this.messageQueue.push(message)
    }
  }

  destroy() {
    this.destroyed = true
    this.stopHeartbeat()
    this.ws?.close(1000, 'Client destroyed')
    this.ws = null
  }

  get readyState(): number {
    return this.ws?.readyState ?? WebSocket.CLOSED
  }
}

```

---

## Vue 3 集成

### 自定义 Composable

```typescript
// composables/useWebSocket.ts
import { ref, onUnmounted, readonly } from 'vue'

interface UseWebSocketOptions {
  onMessage?: (data: unknown) => void
  onOpen?: () => void
  onClose?: (event: CloseEvent) => void
  immediate?: boolean
}

export function useWebSocket(url: string, options: UseWebSocketOptions = {}) {
  const ws = ref<WebSocket | null>(null)
  const isConnected = ref(false)
  const lastMessage = ref<unknown>(null)

  function connect() {
    if (ws.value?.readyState === WebSocket.OPEN) return

    ws.value = new WebSocket(url)

    ws.value.onopen = () => {
      isConnected.value = true
      options.onOpen?.()
    }

    ws.value.onmessage = (event) => {
      try {
        lastMessage.value = JSON.parse(event.data as string)
      } catch {
        lastMessage.value = event.data
      }
      options.onMessage?.(lastMessage.value)
    }

    ws.value.onclose = (event) => {
      isConnected.value = false
      options.onClose?.(event)
    }
  }

  function send(data: unknown) {
    const message = typeof data === 'string' ? data : JSON.stringify(data)
    if (ws.value?.readyState === WebSocket.OPEN) {
      ws.value.send(message)
    }
  }

  function disconnect() {
    ws.value?.close(1000)
    ws.value = null
    isConnected.value = false
  }

  if (options.immediate !== false) {
    connect()
  }

  onUnmounted(() => {
    disconnect()
  })

  return {
    isConnected: readonly(isConnected),
    lastMessage: readonly(lastMessage),
    connect,
    disconnect,
    send
  }
}

```

```html
<!-- 组件中使用 -->
<script setup lang="ts">
import { useWebSocket } from '@/composables/useWebSocket'

const { isConnected, lastMessage, send } = useWebSocket('ws://localhost:8000/ws', {
  onMessage: (data) => {
    console.log('收到消息:', data)
  }
})
</script>

```

### VueUse 的 useWebSocket

VueUse 提供了开箱即用的 `useWebSocket`，支持自动重连和心跳：

```bash
npm install @vueuse/core

```

```typescript
import { useWebSocket } from '@vueuse/core'

const { status, data, send, close } = useWebSocket('ws://localhost:8000/ws', {
  autoReconnect: {
    retries: 5,
    delay: 1000,
    onFailed() {
      console.error('重连失败')
    }
  },
  heartbeat: {
    message: JSON.stringify({ type: 'ping' }),
    interval: 30000,
    pongTimeout: 5000
  }
})

```

VueUse `useWebSocket` 主要参数：

| 参数                    | 类型                              | 默认值      | 说明           |
| --------------------- | ------------------------------- | -------- | ------------ |
| autoReconnect         | boolean \| AutoReconnectOptions | false    | 是否自动重连       |
| autoReconnect.retries | number                          | Infinity | 最大重连次数       |
| autoReconnect.delay   | number                          | 1000     | 重连延迟（毫秒）     |
| heartbeat             | boolean \| HeartbeatOptions     | false    | 是否启用心跳       |
| heartbeat.message     | string                          | 'ping'   | 心跳消息内容       |
| heartbeat.interval    | number                          | 1000     | 心跳间隔（毫秒）     |
| heartbeat.pongTimeout | number                          | 1000     | 等待 pong 超时时间 |
| immediate             | boolean                         | true     | 是否立即连接       |
| onConnected           | (ws: WebSocket) => void         | —        | 连接建立回调       |
| onDisconnected        | (ws, event) => void             | —        | 连接断开回调       |
| onError               | (ws, event) => void             | —        | 错误回调         |
| onMessage             | (ws, event) => void             | —        | 消息回调         |

---

## React 集成

### 自定义 Hook

```typescript
// hooks/useWebSocket.ts
import { useEffect, useRef, useState, useCallback } from 'react'

interface UseWebSocketOptions {
  onMessage?: (data: unknown) => void
  onOpen?: () => void
  onClose?: (event: CloseEvent) => void
}

interface UseWebSocketReturn {
  isConnected: boolean
  lastMessage: unknown
  send: (data: unknown) => void
}

export function useWebSocket(
  url: string,
  options: UseWebSocketOptions = {}
): UseWebSocketReturn {
  const wsRef = useRef<WebSocket | null>(null)
  const [isConnected, setIsConnected] = useState(false)
  const [lastMessage, setLastMessage] = useState<unknown>(null)

  // 用 ref 保存 options 回调，避免 useEffect 依赖频繁变化
  const optionsRef = useRef(options)
  optionsRef.current = options

  useEffect(() => {
    const ws = new WebSocket(url)
    wsRef.current = ws

    ws.onopen = () => {
      setIsConnected(true)
      optionsRef.current.onOpen?.()
    }

    ws.onmessage = (event) => {
      let data: unknown
      try {
        data = JSON.parse(event.data as string)
      } catch {
        data = event.data
      }
      setLastMessage(data)
      optionsRef.current.onMessage?.(data)
    }

    ws.onclose = (event) => {
      setIsConnected(false)
      optionsRef.current.onClose?.(event)
    }

    // 组件卸载时正确关闭连接
    return () => {
      ws.close(1000)
      wsRef.current = null
    }
  }, [url]) // url 变化时重建连接

  const send = useCallback((data: unknown) => {
    const message = typeof data === 'string' ? data : JSON.stringify(data)
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      wsRef.current.send(message)
    }
  }, [])

  return { isConnected, lastMessage, send }
}

```

```typescript
// 组件中使用
function ChatRoom({ roomId }: { roomId: string }) {
  const { isConnected, lastMessage, send } = useWebSocket(
    `ws://localhost:8000/ws/room/${roomId}`,
    {
      onMessage: (data) => {
        console.log('新消息:', data)
      }
    }
  )

  return (
    <div>
      <span>状态: {isConnected ? '已连接' : '未连接'}</span>
      <button onClick={() => send({ type: 'chat', content: 'Hello' })}>
        发送
      </button>
    </div>
  )
}

```

---

## 消息格式设计

### JSON 协议

约定 `type` \+ `payload` 结构，方便在客户端进行消息路由：

```typescript
interface BaseMessage {
  type: string
  id?: string       // 可选消息 ID，用于请求-响应配对
  timestamp?: number
}

interface ChatMessage extends BaseMessage {
  type: 'chat'
  payload: {
    roomId: string
    content: string
    userId: string
  }
}

interface NotificationMessage extends BaseMessage {
  type: 'notification'
  payload: {
    level: 'info' | 'warning' | 'error'
    text: string
  }
}

type AppMessage = ChatMessage | NotificationMessage

// 消息分发器
function handleMessage(raw: unknown) {
  const message = raw as AppMessage
  switch (message.type) {
    case 'chat':
      handleChat(message.payload)
      break
    case 'notification':
      handleNotification(message.payload)
      break
    default:
      console.warn('未知消息类型:', message)
  }
}

```

### 二进制协议

使用 `ArrayBuffer` \+ `DataView` 构建紧凑的二进制帧：

```typescript
// 协议定义：[消息类型(1字节)] [消息ID(4字节)] [数据长度(4字节)] [数据(N字节)]

function encodeMessage(type: number, id: number, data: Uint8Array): ArrayBuffer {
  const buffer = new ArrayBuffer(9 + data.byteLength)
  const view = new DataView(buffer)

  view.setUint8(0, type)          // 1 字节：消息类型
  view.setUint32(1, id)           // 4 字节：消息 ID（大端序）
  view.setUint32(5, data.byteLength) // 4 字节：数据长度

  // 写入数据
  const dataView = new Uint8Array(buffer, 9)
  dataView.set(data)

  return buffer
}

function decodeMessage(buffer: ArrayBuffer) {
  const view = new DataView(buffer)
  const type = view.getUint8(0)
  const id = view.getUint32(1)
  const dataLength = view.getUint32(5)
  const data = new Uint8Array(buffer, 9, dataLength)

  return { type, id, data }
}

// 接收二进制消息
ws.binaryType = 'arraybuffer'  // 默认为 'blob'，需要改成 'arraybuffer'

ws.onmessage = (event) => {
  if (event.data instanceof ArrayBuffer) {
    const { type, id, data } = decodeMessage(event.data)
    console.log(type, id, data)
  }
}

```

---

## 服务端（Python FastAPI）

### 基础 WebSocket 路由

```python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
import json

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    try:
        while True:
            # 接收文本消息
            data = await websocket.receive_text()
            message = json.loads(data)

            # 回复消息
            await websocket.send_text(json.dumps({
                "type": "echo",
                "payload": message
            }))
    except WebSocketDisconnect:
        print("客户端断开连接")

```

### 广播模式

```python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from typing import List
import json

app = FastAPI()

class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)

    async def broadcast(self, message: dict):
        data = json.dumps(message)
        for connection in self.active_connections:
            await connection.send_text(data)

manager = ConnectionManager()

@app.websocket("/ws/broadcast")
async def broadcast_endpoint(websocket: WebSocket):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            message = json.loads(data)
            await manager.broadcast(message)
    except WebSocketDisconnect:
        manager.disconnect(websocket)

```

### Room 模式

```python
from collections import defaultdict
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
import json

app = FastAPI()

class RoomManager:
    def __init__(self):
        # room_id -> List[WebSocket]
        self.rooms: dict[str, list[WebSocket]] = defaultdict(list)

    async def join(self, room_id: str, websocket: WebSocket):
        await websocket.accept()
        self.rooms[room_id].append(websocket)

    def leave(self, room_id: str, websocket: WebSocket):
        self.rooms[room_id].remove(websocket)
        if not self.rooms[room_id]:
            del self.rooms[room_id]

    async def broadcast_to_room(self, room_id: str, message: dict):
        data = json.dumps(message)
        for connection in self.rooms.get(room_id, []):
            await connection.send_text(data)

room_manager = RoomManager()

@app.websocket("/ws/room/{room_id}")
async def room_endpoint(websocket: WebSocket, room_id: str):
    await room_manager.join(room_id, websocket)
    try:
        while True:
            data = await websocket.receive_text()
            message = json.loads(data)
            await room_manager.broadcast_to_room(room_id, message)
    except WebSocketDisconnect:
        room_manager.leave(room_id, websocket)

```

---

## 最佳实践

### 连接状态管理

在发送消息前始终检查 `readyState`，避免向未就绪的连接发送消息：

```typescript
function safeSend(ws: WebSocket, data: unknown) {
  if (ws.readyState !== WebSocket.OPEN) {
    console.warn(`连接状态为 ${ws.readyState}，消息发送失败`)
    return false
  }
  ws.send(typeof data === 'string' ? data : JSON.stringify(data))
  return true
}

```

### 请求-响应模式

为每条消息分配 ID，实现类 RPC 的请求-响应配对：

```typescript
class WebSocketRPC {
  private ws: WebSocket
  private pending = new Map<string, (response: unknown) => void>()

  constructor(url: string) {
    this.ws = new WebSocket(url)
    this.ws.onmessage = (event) => {
      const message = JSON.parse(event.data as string)
      if (message.id && this.pending.has(message.id)) {
        const resolve = this.pending.get(message.id)!
        this.pending.delete(message.id)
        resolve(message.payload)
      }
    }
  }

  call(type: string, payload: unknown): Promise<unknown> {
    return new Promise((resolve, reject) => {
      const id = crypto.randomUUID()
      this.pending.set(id, resolve)

      // 超时处理
      setTimeout(() => {
        if (this.pending.has(id)) {
          this.pending.delete(id)
          reject(new Error(`请求 ${id} 超时`))
        }
      }, 10000)

      this.ws.send(JSON.stringify({ type, id, payload }))
    })
  }
}

```

---

## 踩坑与注意事项

### 跨域配置

WebSocket 握手基于 HTTP，受同源策略约束。服务端需要验证 `Origin` 请求头：

```python
# FastAPI 允许指定来源
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

```

### Nginx 代理 WebSocket 必须添加 Upgrade 头

普通 Nginx 反向代理不支持 WebSocket，必须添加以下配置：

```nginx
location /ws {
    proxy_pass http://backend:8000;
    proxy_http_version 1.1;

    # 这两行是关键，缺少任意一行都会导致 WebSocket 升级失败
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;

    # WebSocket 长连接，需要适当增大超时时间
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}

```

### 连接数限制

浏览器对同一域名的 WebSocket 连接数有限制（通常约 256 个），服务器操作系统对文件描述符数量也有限制。大量并发连接场景需要：

- 调整系统文件描述符上限（`ulimit -n`）
- 使用连接池或复用单个连接（multiplexing）
- 考虑使用 WebSocket 网关（如 APISIX、Nginx Plus）

### 页面隐藏时的连接行为

部分浏览器会在页面进入后台时降低 WebSocket 的优先级甚至断开连接，需要监听 `visibilitychange` 事件重连：

```typescript
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible') {
    if (ws.readyState === WebSocket.CLOSED) {
      // 重新建立连接
      reconnect()
    }
  }
})

```

### 避免在 React StrictMode 下重复建立连接

React StrictMode 在开发环境中会刻意挂载组件两次，导致 `useEffect` 触发两次，产生两个 WebSocket 连接。确保 cleanup 函数正确关闭连接：

```typescript
useEffect(() => {
  const ws = new WebSocket(url)
  // ...

  // cleanup 函数必须关闭连接
  return () => {
    ws.close(1000)
  }
}, [url])

```

---

## 常见陷阱

### 陷阱：WebSocket 连接在移动端或弱网下频繁断开

**现象：** 移动端 App 切换到后台或网络抖动后，WebSocket 静默断开，服务端无感知，消息丢失。  
**原因：** 大多数代理、负载均衡器（Nginx 默认 60s）和移动 OS 会在空闲超时后断开连接，且不发送 `close` 帧。  
**解决：** 客户端实现心跳机制（每 30s 发送 `ping` 消息），服务端响应 `pong`；同时实现断线自动重连（指数退避）：

```javascript
let reconnectDelay = 1000;
function connect() {
  const ws = new WebSocket(url);
  ws.onclose = () => {
    setTimeout(connect, reconnectDelay);
    reconnectDelay = Math.min(reconnectDelay * 2, 30000);
  };
  ws.onopen = () => { reconnectDelay = 1000; };
}

```

### 陷阱：跨域 WebSocket 连接被阻止

**现象：** 浏览器控制台报 `WebSocket connection to 'wss://...' failed`，HTTP 握手返回 403。  
**原因：** WebSocket 握手是 HTTP 请求，服务端没有校验 `Origin` 头导致拒绝，或反向代理未转发 `Upgrade: websocket` 请求头。  
**解决：** 服务端配置允许的 `Origin`，Nginx 配置添加：

```nginx
location /ws {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

```

### 陷阱：发送大量消息时内存无限增长

**现象：** 高频发送消息时内存持续增长，最终崩溃。  
**原因：** 发送速度超过网络吞吐时，消息积压在 `bufferedAmount` 缓冲区，若不检查缓冲状态就持续调用 `send()`，缓冲区无限膨胀。  
**解决：** 发送前检查 `ws.bufferedAmount`，缓冲区积压超阈值时暂停发送或丢弃低优先级消息：

```javascript
if (ws.bufferedAmount < 1024 * 64) {
  ws.send(data);
}

```

---

## 参见

[Axios完全指南](https://blog.vercanti.com/axios-wan-quan-zhi-nan/)  
[Vue3入门](https://blog.vercanti.com/vue-3-ru-men-zhi-nan/)