深入浅出 WebSocket 协议

目录

一、WebSocket 概念

WebSocket 是"实时交互通道",不是"状态通知工具"。

WebSocket 的本质(先纠正 90% 的误解):

  • WebSocket = 基于 HTTP 握手 + 基于 TCP 的全双工应用层协议

为什么 WebSocket 会被发明?

HTTP 的根本问题只有一个:

  • 服务端不能主动说话

所有"实时方案"的演进路径:

typescript 复制代码
短轮询
→ 长轮询
→ HTTP Streaming
→ SSE
→ WebSocket

WebSocket 是 "彻底摆脱请求-响应模型" 的方案。

二、WebSocket 生命周期

typescript 复制代码
CONNECTING
→ OPEN
→ CLOSING
→ CLOSED

前端状态映射:

typescript 复制代码
WebSocket.CONNECTING = 0
WebSocket.OPEN       = 1
WebSocket.CLOSING    = 2
WebSocket.CLOSED     = 3

三、websocket 协议请求过程(⭐️⭐️⭐️⭐️⭐️)

0、整体流程总览(先有一张"图")

一句话描述 websocket 请求过程:

  • WebSocket = HTTP Upgrade 握手 + 基于 TCP 的长连接 + 自定义的全双工二进制帧协议
typescript 复制代码
前端 JS
  ↓
HTTP Upgrade 请求
  ↓
服务器 101 Switching Protocols
  ↓
WebSocket 连接建立
  ↓
数据帧双向通信
  ↓
Close 帧关闭连接

‼️ 关键认知:

  • WebSocket 的"第一次请求"本质是 HTTP

1、第 1 步:前端发起 WebSocket 请求

typescript 复制代码
const ws = new WebSocket('wss://example.com/ws')

浏览器内部发生的事:

  • 并不是直接走 WebSocket
  • 而是先发一个 HTTP 请求
  • 请求头中带 Upgrade: websocket

2、第 2 步:HTTP → WebSocket Upgrade 请求(重点)

客户端请求(HTTP):

bash 复制代码
GET /ws HTTP/1.1
Host: example.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: https://example.com

🔥 各字段含义(面试必问):

Header 作用
Upgrade: websocket 请求升级协议
Connection: Upgrade 告诉中间层不能降级
Sec-WebSocket-Key 随机字符串,用于校验
Sec-WebSocket-Version 协议版本(固定 13)
Origin 安全校验(CORS 类似)

3、第 3 步:服务端返回 101(协议切换)

服务端响应:

bash 复制代码
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

核心校验逻辑(重要):

typescript 复制代码
Sec-WebSocket-Accept =
base64(
  SHA1(
    Sec-WebSocket-Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
  )
)

目的:

  • 防止普通 HTTP 客户端伪造 WebSocket 请求

到这里为止,发生了什么本质变化?

对比 HTTP WebSocket
连接 一次一请求 长连接
协议 文本 二进制帧
状态 无状态 有状态
通道 请求-响应 全双工

📌 HTTP 协议生命周期在 101 之后结束

4、第 4 步:WebSocket 数据帧通信(真正开始)

WebSocket 帧结构(理解级):

typescript 复制代码
| FIN | OPCODE | MASK | Payload length | Payload data |

常见 OPCODE:

Opcode 含义
0x1 文本帧
0x2 二进制帧
0x8 关闭
0x9 Ping
0xA Pong

前端发送的本质:

  • ws.send(JSON.stringify({ type: 'CHAT', msg: 'hi' }))

会被浏览器封装成 WebSocket Frame

5、第 5 步:心跳(Ping / Pong)

为什么一定要心跳?

  • NAT / 防火墙会回收空闲 TCP
  • 服务端无法感知客户端是否"假死"

典型策略:

方向 类型
Client → Server Ping
Server → Client Pong

⚠️ 浏览器 API 不允许手发 Ping 帧

  • 只能业务层发"心跳消息"

6、第 6 步:关闭连接(Close 帧)

前端主动关闭:

typescript 复制代码
ws.close(1000, 'normal close')

协议层流程:

typescript 复制代码
Client → Close Frame
Server → Close Frame
TCP 连接关闭

常见 Close Code:

Code 含义
1000 正常
1006 异常断开
1008 策略违规

7、抓包视角(非常加分)

typescript 复制代码
Chrome DevTools → Network → WS

你会看到:

  • 第一条:HTTP 101
  • 后续:Message / Frame
  • Ping / Pong
  • Close

🔥 这是 WebSocket 面试"王炸现场证明"

四、前端 WebSocket 工程级实现(重点)

1、正确的职责划分(非常重要)

❌ 错误:

typescript 复制代码
React/Vue Component
  └── new WebSocket()

✅ 正确:

typescript 复制代码
WebSocketService
  ↓
State Store(Pinia / Zustand)
  ↓
UI

2、前端使用 WebSocket 的最小正确姿势

typescript 复制代码
const ws = new WebSocket('wss://api.example.com/ws')

ws.onopen = () => console.log('connected')

ws.onmessage = (e) => {
  const msg = JSON.parse(e.data)
  console.log(msg)
}

ws.onerror = console.error
ws.onclose = () => console.log('closed')

⚠️ 这段代码只能算"能跑",不能上生产

3、前端 WebSocket 必备技能(核心)(⭐️⭐️⭐️⭐️⭐️)

前端 WebSocket 必备的 7 大核心能力:

  • 连接生命周期管理(自动重连 + 心跳机制)
  • 在线 / 离线状态感知
  • 消息协议与版本演进
  • 消息可靠性保障(消息 ACK / 去重)
  • 连接复用与资源治理
  • 架构解耦与职责分离(与 UI 解耦)
  • 消息分发与消费模型

这 7 大能力不是并列的,而是"自底向上、逐层依赖"的能力栈:

typescript 复制代码
重连/心跳
  ↓
在线感知
  ↓
复用连接
  ↓
协议结构
  ↓
ACK / 去重
  ↓
消息路由
  ↓
UI 解耦

(1)、连接生命周期管理

连接生命周期管理是前端 WebSocket 的基石,核心就是"断线自动重连 + 心跳检测",确保连接长期可用、稳定可靠,同时可结合页面可见性和在线状态优化资源。

定义:

  • 连接生命周期管理 = 保证 WebSocket 连接能长期存活、可恢复、稳定可靠,无论网络波动、浏览器状态如何。

核心目标:

  • 连接 断线自动恢复
  • 连接 不死锁 / 假死
  • 尽量节省资源,避免频繁重连
  • 在不同浏览器状态下(前台 / 后台 / 离线)仍然安全

WebSocket 自身只是 TCP + 升级协议,不提供:

  • 自动重连
  • 连接状态感知
  • 心跳检测

现实网络环境中:

场景 问题 后果
网络抖动 / 切换 Wi-Fi TCP 断开,浏览器不报错 消息丢失,UI 假死
页面切后台 某些浏览器暂停 JS 连接可能超时
心跳缺失 NAT / 防火墙断开 长连接被意外切断

因此自动重连 + 心跳机制是前端 WebSocket 的必做能力。

核心模块:

  • 自动重连(指数退避)
  • 心跳机制
①、自动重连(指数退避)

目标:

  • 避免网络短断导致消息丢失,减少服务器压力

策略:

  • 断线后立即尝试重连 → 如果失败 → 延迟重试
  • 延迟逐步增加 → 指数退避
  • 最大重试次数 / 最大延迟限制

伪代码示例:

typescript 复制代码
let attempt = 0

function reconnect() {
  const delay = Math.min(1000 * 2 ** attempt, 30000) // 最大30s
  setTimeout(() => {
    ws = new WebSocket(url)
    ws.onopen = () => { attempt = 0 } // 成功后重置
    ws.onclose = () => { attempt++; reconnect() }
  }, delay)
}
②、心跳机制(必做)

目标:

  • 检测连接是否仍然活着

策略:

  • 客户端定时发送心跳(或 Ping 消息)
  • 服务端返回 Pong / ACK
  • 超时未响应 → 判定连接失效 → 自动重连

示例:

typescript 复制代码
function startHeartbeat(ws: WebSocket) {
  const interval = 10000 // 每10秒
  const timer = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({ type: "PING" }))
    } else {
      clearInterval(timer)
    }
  }, interval)
}
③、与状态感知结合(可选高级)
  • 页面可见性 → 暂停或加长心跳间隔
  • navigator.onLine → 离线暂停重连,在线恢复
④、连接生命周期管理总结表
能力 核心目标 实现方式
自动重连 网络断开自动恢复 指数退避 + 最大重试次数
心跳机制 检测假死 / NAT 超时 定时 PING + 超时重连
状态感知 节省资源 / 避免错误重连 页面可见性 / 在线状态

(2)、在线 / 离线状态感知

"在线 / 离线状态感知是前端 WebSocket 的能力之一,它通过网络状态和页面可见性来判断是否维持连接或暂停心跳,从而保证长连接稳定、节省资源,并结合重连机制处理网络恢复。"

在线 / 离线状态感知:

  • 前端能够感知用户网络状态和页面可见性,从而决定 WebSocket 是否需要维持、重连或暂停连接。

核心目标:

  • 避免在用户离线或后台时浪费连接资源
  • 提高 WebSocket 稳定性(减少假死 / 意外断开)
  • 优化用户体验(断网/恢复网时消息可靠性)

WebSocket 本身只保证 TCP 层的传输可靠,但不关注:

  • 用户是否离线
  • 浏览器是否切后台
  • 网络切换(Wi-Fi ↔ 蜂窝网络)

现实问题:

场景 问题 后果
用户断网 TCP 假死,消息无法发送 消息丢失或重连失败
页面切后台 部分浏览器暂停 JS 心跳/重连不触发 → 长连接被断开
网络切换 IP 变化导致 TCP 断开 需要重新建立连接

所以必须在 SDK 层感知在线/离线状态,并结合连接生命周期管理做决策。

核心模块:

  • 离线状态感知
  • 页面可见性处理
①、网络状态感知
  • 使用浏览器 API:
typescript 复制代码
window.addEventListener("online", () => {
  console.log("网络已连接")
  ws.reconnect()
})

window.addEventListener("offline", () => {
  console.log("网络已断开")
  ws.pause() // 暂停发送或心跳
})
  • navigator.onLine 可获取初始状态
  • 可以结合 心跳 + ACK 做二次判断
②、页面可见性感知
  • 使用 Page Visibility API:
typescript 复制代码
document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "visible") {
    ws.resume() // 恢复心跳 / 重连
  } else {
    ws.pauseHeartbeat() // 暂停心跳,节省资源
  }
})
  • 后台页可适当延长心跳间隔,降低 CPU / 网络消耗
③、与连接生命周期结合
  • 在线 + 页面可见 → 保持连接 + 正常心跳
  • 离线 / 页面隐藏 → 暂停或降低心跳频率
  • 网络恢复 → 自动重连 + 发送未 ACK 消息
④、微小示例(结合 WebSocket SDK)
typescript 复制代码
class WSClient {
  ws: WebSocket
  online: boolean = navigator.onLine

  constructor(url: string) {
    this.initWS(url)
    this.setupListeners()
  }

  initWS(url: string) {
    this.ws = new WebSocket(url)
    this.ws.onopen = () => console.log("连接已建立")
    this.ws.onclose = () => {
      if (this.online) this.reconnect()
    }
  }

  setupListeners() {
    window.addEventListener("online", () => {
      this.online = true
      console.log("网络恢复,尝试重连")
      this.reconnect()
    })

    window.addEventListener("offline", () => {
      this.online = false
      console.log("网络断开,暂停心跳")
      this.pauseHeartbeat()
    })

    document.addEventListener("visibilitychange", () => {
      if (document.visibilityState === "visible") this.resumeHeartbeat()
      else this.pauseHeartbeat()
    })
  }

  reconnect() { /* 指数退避重连逻辑 */ }
  pauseHeartbeat() { /* 停止心跳 */ }
  resumeHeartbeat() { /* 恢复心跳 */ }
}

特点:

  • 网络断开 → 自动暂停
  • 网络恢复 → 自动重连
  • 页面切后台 → 调整心跳
  • 页面切前台 → 恢复心跳
⑤、总结表格
能力 核心目标 实现方式
网络状态感知 判断在线 / 离线 window.online/offline
页面可见性感知 节省资源 / 避免后台假死 document.visibilitychange
与连接生命周期结合 自动重连 / 心跳调整 SDK 中的重连 + 心跳逻辑

(3)、连接复用与资源治理

连接复用与资源治理是前端 WebSocket 的能力之一,通过单例或连接池统一管理 WebSocket,结合消息总线让多个模块共享连接,同时统一处理心跳、重连和消息分发,从而节省资源并提高系统稳定性。

连接复用与资源治理:

  • 前端通过 复用同一 WebSocket 连接服务多个模块/业务,并合理管理连接资源(心跳、重连、断开),避免资源浪费和重复连接。

核心目标:

  • 避免每个模块单独创建 WebSocket → 节省 TCP 连接和浏览器资源
  • 提高消息分发效率 → 单一连接即可服务多模块
  • 管理连接生命周期 → 心跳、重连统一管理
  • 支持模块动态订阅/退订 → 高度灵活

如果不复用连接,常见问题:

问题 后果
每个模块都创建独立连接 消耗 TCP 资源,心跳 / 重连重复计算
多模块共享消息没有路由 消息处理混乱,UI 难维护
未统一管理 重连策略不同,容易出现假死或资源泄露

企业级系统中,尤其 IM / 实时推送系统,单页面多模块共享单个 WebSocket 是标配

①、单例 / 连接池
  • 单例连接:整个页面只有一个 WebSocket 实例,所有模块共享
  • 连接池(高级):业务量大时,可以维护少量长连接,按模块 / topic 分配
typescript 复制代码
// 单例模式示例
class WSConnectionManager {
  private static instance: WSConnection
  static getInstance(url: string) {
    if (!WSConnectionManager.instance) {
      WSConnectionManager.instance = new WSConnection(url)
    }
    return WSConnectionManager.instance
  }
}
②、动态订阅 / 退订
  • 模块只订阅自己关心的消息
  • 不需要关闭整个连接 → 节省资源
typescript 复制代码
const ws = WSConnectionManager.getInstance("wss://example.com/ws")
const unsubChat = ws.bus.subscribe("CHAT", msg => console.log("聊天消息:", msg.payload))
const unsubOrder = ws.bus.subscribe("ORDER", msg => console.log("订单消息:", msg.payload))

// 模块卸载
unsubChat()
③、统一资源治理
  • 心跳和重连统一管理
  • 模块无需关心连接状态
  • 避免重复心跳、重复重连
typescript 复制代码
class WSConnection {
  private heartbeatTimer: any
  startHeartbeat() { /* 统一心跳逻辑 */ }
  stopHeartbeat() { /* 暂停 */ }
}
④、多模块共享消息分发
  • 消息总线分发 → 每个模块独立消费
  • 支持消息 ACK / 去重 / 顺序控制
  • ws.bus.publish(msg) // 单条消息推送到所有订阅模块
⑤、微小示例(结合前面的总线示例)
typescript 复制代码
// WSClient 是单例连接
const ws = WSClient.getInstance("wss://example.com/ws")

// 模块 A
ws.subscribe("CHAT", msg => console.log("模块A聊天消息:", msg.payload))

// 模块 B
ws.subscribe("ORDER_STATUS", msg => console.log("模块B订单状态:", msg.payload))

// 动态退订
const unsub = ws.subscribe("NOTIFICATION", msg => console.log(msg))
unsub() // 模块卸载时退订

特点:

  • 一个连接服务多个模块
  • 动态订阅 / 退订
  • 心跳、重连、消息分发统一管理
  • 资源高效利用
⑥、总结表格
能力 核心目标 实现方式
单例 / 连接池 避免重复连接 单例模式或少量连接池
消息总线 多模块共享消息 Dispatcher / PubSub
动态订阅 / 退订 模块自由挂载卸载 subscribe/unsubscribe
统一心跳/重连 节省资源 / 提升稳定性 SDK 统一管理
消息可靠性 不丢失 / 去重 / 顺序 ACK + msgId + seq

(4)、消息协议与版本演进

前端 WebSocket 的消息协议设计不仅要标准化字段、支持 ACK / 去重 / 顺序,还必须设计协议版本号以支持平滑升级。通过向前、向后兼容策略,可以保证新老客户端共存,确保消息可靠性和系统可扩展性。

消息协议与版本演进:

  • WebSocket 消息协议 = 前端与服务端约定的 消息格式、字段、类型、ACK、心跳、序列号等规则
  • 版本演进 = 当协议更新时,能兼容旧客户端、支持新功能,同时保证可靠性和可扩展性

核心目标:

  • 保证前后端通信一致 → 避免解析错误
  • 支持新功能 → 不破坏已有模块
  • 支持消息可靠性机制 → ACK、去重、顺序
  • 可扩展性强 → 新模块可以接入,不影响老模块

为什么重要?

  • 不随便发 JSON:随意 JSON 无法保证版本兼容、ACK、去重、顺序等
  • 业务复杂时:一个连接可能有多模块多业务,消息字段多,必须标准化
  • 升级迭代:如果协议没有版本管理,客户端升级后可能无法解析旧消息
  • 可靠性保障:消息 ACK / 去重 / 顺序需要协议支持

所以企业级 WebSocket 消息协议设计是 SDK 核心能力之一。

核心设计原则:

①、标准化字段

每条消息统一格式,例如:

typescript 复制代码
interface WSMessage {
  version: number        // 协议版本
  type: string           // 消息类型
  msgId: string          // 消息唯一 ID(去重)
  seq?: number           // 顺序号
  payload: any           // 业务数据
  timestamp?: number     // 时间戳
}
  • version → 协议版本,兼容不同客户端
  • msgId + seq → ACK / 去重 / 顺序
  • type → 消息路由
  • payload → 业务层数据
②、ACK / 去重 / 顺序机制
  • 消息协议必须支持 应用层 ACK
  • msgId 用于去重
  • seq 用于顺序敏感业务
typescript 复制代码
// ACK 示例
{
  type: "ACK",
  ackMsgId: "uuid-001",
  status: "RECEIVED",
  version: 2
}
③、协议版本管理
  • 前端解析消息时根据 version 判断如何处理
  • 服务端可以同时维护多版本消息
  • 升级策略:
类型 做法
向前兼容 新字段添加在 payload 内,可选,不影响旧客户端
向后兼容 保留旧字段,老客户端可忽略新字段
强制升级 协议版本差异过大 → 提示客户端更新
④、模块化 / 类型化
  • 每个消息类型独立定义结构
  • 支持多模块消费同一连接消息
  • 避免混乱的"万能 JSON"
typescript 复制代码
// 聊天消息
interface ChatMessage {
  text: string
  fromUserId: string
  toUserId?: string
}

// 订单消息
interface OrderStatusMessage {
  orderId: string
  status: "PAID" | "CANCELLED"
}
⑤、微小示例
typescript 复制代码
const wsMessage = {
  version: 2,
  type: "CHAT",
  msgId: "uuid-123",
  seq: 1001,
  timestamp: Date.now(),
  payload: {
    text: "hello",
    fromUserId: "u001",
  }
}

// 客户端解析
function handleMessage(msg) {
  if (msg.version === 1) {
    // 旧协议解析
  } else if (msg.version === 2) {
    // 新协议解析
  }
}
⑥、协议演进策略总结
方向 原则 举例
新字段添加 可选字段,不破坏旧客户端 payload 新增 avatarUrl
字段修改 保留旧字段,标记废弃 status 新增值,不删除旧值
消息类型增加 新模块订阅新类型 NOTIFICATION
强制升级 协议版本号大幅变化 老客户端提示更新

(5)、消息可靠性保障

消息可靠性保障 = 消息 ACK + 去重 + 顺序控制 + 重连重发,保证前端业务逻辑在网络抖动下也能正确运行。

定义:

  • 消息可靠性保障 = 确保消息 不丢、不重复、按需确认,在前端 WebSocket 上,核心是 消息 ACK、去重和顺序控制。

目的:

  • 避免消息丢失 → 用户体验或业务状态错误
  • 避免重复处理 → 数据幂等问题
  • 确保顺序 → 订单状态、聊天消息等顺序敏感

WebSocket 本身只保证 TCP 层传输可靠:

  • TCP 保证 数据到达,但:
    • 浏览器刷新 / 网络切换 → WebSocket 断开
    • 消息到达客户端,但应用层未处理 → 可能丢失
  • TCP 不提供 应用级 ACK → 无法保证业务正确性
  • TCP 不提供 去重 / 顺序业务层处理 → 必须在应用层处理

所以可靠性必须在 前端 SDK / 消息协议层实现。

前端可靠性策略汇总表:

机制 目的 实现方式
消息 ACK 确认已收到 客户端返回 ACK / 服务端重发未 ACK
消息去重 避免重复处理 msgId / seq + Set / Map
顺序控制 顺序敏感 seq / timestamp + 缓存重排
心跳检测 避免假死 Ping / Pong 或业务心跳
重连 & 重发 网络断开 指数退避 + 未 ACK 消息重发

核心模块:

  • 消息 ACK(避免消息丢失)→ 用户体验或业务状态错误
  • 消息去重→ 数据幂等问题
  • 顺序控制→ 订单状态、聊天消息等顺序敏感
①、消息 ACK(确认机制)
  • 服务端发送消息后,客户端 返回确认
  • 客户端处理成功才算 ACK
  • 服务端可重发未 ACK 消息

协议设计示例:

typescript 复制代码
// 消息
{
  type: "CHAT",
  msgId: "uuid-001",
  payload: { text: "hello" }
}

// ACK
{
  type: "ACK",
  ackMsgId: "uuid-001",
  status: "RECEIVED"
}
②、消息去重
  • 避免同一条消息被多次处理
  • 通常用 msgId 或 seq 去重
typescript 复制代码
const receivedIds = new Set()

function handleMessage(msg) {
  if (receivedIds.has(msg.msgId)) return
  receivedIds.add(msg.msgId)
  // 正常处理
}
③、顺序保证(可选 / 业务层)
  • 对顺序敏感的业务,需要 seq 或 timestamp
  • 超前 / 落后的消息可能缓存或重排
typescript 复制代码
let lastSeq = 0

function handleMessage(msg) {
  if (msg.seq <= lastSeq) return // 丢弃重复或落后
  lastSeq = msg.seq
  process(msg)
}
④、微小示例
typescript 复制代码
class ReliableWS {
  ws: WebSocket
  pending: Map<string, any> = new Map()
  receivedIds: Set<string> = new Set()

  constructor(url: string) {
    this.ws = new WebSocket(url)
    this.ws.onmessage = (e) => this.onMessage(JSON.parse(e.data))
  }

  send(msg: any) {
    this.pending.set(msg.msgId, msg)
    this.ws.send(JSON.stringify(msg))
  }

  onMessage(msg: any) {
    // 处理 ACK
    if (msg.type === "ACK") {
      this.pending.delete(msg.ackMsgId)
      return
    }

    // 去重
    if (this.receivedIds.has(msg.msgId)) return
    this.receivedIds.add(msg.msgId)

    // 顺序处理(简单示例)
    this.process(msg)

    // 返回 ACK
    this.ws.send(JSON.stringify({ type: "ACK", ackMsgId: msg.msgId }))
  }

  process(msg: any) {
    console.log("处理消息:", msg)
  }
}

(6)、消息分发与消费模型

前端 WebSocket 的消息分发与消费模型,就是"把 WebSocket 收到的消息通过总线路由到各模块订阅者,实现多模块解耦、可扩展和可靠消费"。

在前端 WebSocket SDK / Service 中,消息不能直接丢给 UI,否则会出现:

  • UI 逻辑耦合 WebSocket
  • 多个模块竞争同一条消息
  • 难以处理不同类型消息
  • 消息丢失或重复处理

所以需要一个分层设计:

typescript 复制代码
WebSocket Connection
        ↓
   消息总线 / Dispatcher
        ↓
  模块 / 组件订阅消息

核心思想:

  • 消息路由
    • 根据消息类型或 topic 分发给不同模块
    • 类似"事件类型映射表"
  • 订阅 / 发布
    • 模块主动订阅自己关心的消息类型
    • WebSocket 收到消息后发布到总线
  • 多模块消费同一连接消息
    • 一个 WebSocket 连接可以服务多个模块
    • 每个模块只关心自己订阅的消息

微小示例

假设我们的 WebSocket 收到不同类型的消息:

typescript 复制代码
interface WSMessage {
  type: string
  payload: any
}
①、创建一个简单消息总线(Dispatcher)
typescript 复制代码
type Callback = (msg: WSMessage) => void

class MessageBus {
  private handlers: Record<string, Callback[]> = {}

  subscribe(type: string, cb: Callback) {
    if (!this.handlers[type]) this.handlers[type] = []
    this.handlers[type].push(cb)
    return () => this.unsubscribe(type, cb)
  }

  unsubscribe(type: string, cb: Callback) {
    this.handlers[type] = (this.handlers[type] || []).filter(h => h !== cb)
  }

  publish(msg: WSMessage) {
    (this.handlers[msg.type] || []).forEach(cb => cb(msg))
  }
}
②、WebSocket 封装
typescript 复制代码
class WSClient {
  private ws: WebSocket
  private bus = new MessageBus()

  constructor(url: string) {
    this.ws = new WebSocket(url)
    this.ws.onmessage = (e) => {
      const msg: WSMessage = JSON.parse(e.data)
      this.bus.publish(msg)  // 核心:总线分发
    }
  }

  subscribe(type: string, cb: Callback) {
    return this.bus.subscribe(type, cb)
  }

  send(msg: WSMessage) {
    this.ws.send(JSON.stringify(msg))
  }
}
③、多模块消费示例
typescript 复制代码
const client = new WSClient('wss://example.com/ws')

// 模块 A 关心聊天消息
client.subscribe('CHAT', (msg) => {
  console.log('模块 A 收到聊天消息:', msg.payload)
})

// 模块 B 关心订单状态
client.subscribe('ORDER_STATUS', (msg) => {
  console.log('模块 B 收到订单消息:', msg.payload)
})

✅ 说明:

  • 一个 WebSocket 连接
  • 多个模块可以独立订阅消息
  • WebSocket 与模块 UI 完全解耦
  • 消息类型路由 + Pub/Sub 完整实现

(7)、架构解耦与职责分离

架构解耦与职责分离 = 把 WebSocket 的连接管理、消息总线、业务接口、UI 消费层彻底拆开,让每一层只负责自己的职责,实现可复用、可测试、多模块共享、低耦合的系统。

定义:

  • 架构解耦 = WebSocket 与 UI / 页面业务逻辑彻底分离
  • 职责分离 = 每个模块只关心自己的消息,不直接操作连接

核心目标:

  • 消息逻辑与 UI 无耦合 → 页面刷新 / 模块拆分不影响连接
  • 可测试 / 可扩展 → SDK 层统一处理连接、协议、重连、ACK
  • 支持多模块共享同一连接 → 节省资源、便于管理

如果不解耦,常见问题:

问题 后果
UI 直接用 ws.onmessage 模块逻辑耦合,难复用、难维护
每个模块创建独立连接 资源浪费,心跳 / 重连难管理
消息处理混乱 ACK / 去重难实现,顺序可能错乱

架构解耦与职责分离示例:

①、封装 WebSocket 客户端
  • 负责:连接、重连、心跳、消息收发
  • 不做业务处理,只做基础功能
typescript 复制代码
class WSConnection {
  ws: WebSocket
  onMessage?: (msg: any) => void

  constructor(url: string) {
    this.ws = new WebSocket(url)
    this.ws.onmessage = e => this.onMessage && this.onMessage(JSON.parse(e.data))
  }

  send(msg: any) { this.ws.send(JSON.stringify(msg)) }
}
②、消息总线 / Dispatcher
  • 负责:消息路由、订阅 / 发布
  • 让多个模块独立消费消息
typescript 复制代码
type Callback = (msg: any) => void

class MessageBus {
  private handlers: Record<string, Callback[]> = {}

  subscribe(type: string, cb: Callback) {
    if (!this.handlers[type]) this.handlers[type] = []
    this.handlers[type].push(cb)
    return () => this.unsubscribe(type, cb)
  }

  unsubscribe(type: string, cb: Callback) {
    this.handlers[type] = (this.handlers[type] || []).filter(h => h !== cb)
  }

  publish(msg: any) {
    (this.handlers[msg.type] || []).forEach(cb => cb(msg))
  }
}
③、SDK / Service 层(业务层接口)
  • 负责:提供模块调用接口
  • 不关心 WebSocket 底层实现
  • 可以组合 可靠性 + 消息协议 + 分发
typescript 复制代码
class WSClient {
  private conn: WSConnection
  private bus = new MessageBus()

  constructor(url: string) {
    this.conn = new WSConnection(url)
    this.conn.onMessage = msg => this.bus.publish(msg)
  }

  send(msg: any) { this.conn.send(msg) }

  subscribe(type: string, cb: Callback) { return this.bus.subscribe(type, cb) }
}
④、UI / 业务层(消费层)
typescript 复制代码
const client = new WSClient("wss://example.com/ws")

// 聊天模块订阅聊天消息
client.subscribe("CHAT", msg => console.log("聊天模块收到:", msg.payload))

// 订单模块订阅订单状态
client.subscribe("ORDER_STATUS", msg => console.log("订单模块收到:", msg.payload))

特点:

  • UI 与连接解耦
  • 多模块共享同一连接
  • 模块只关心自己消息
  • SDK 可复用 / 可测试
⑤、额外加分点
  • 统一管理心跳 / 重连 / ACK → 模块无需关心
  • 支持动态订阅 / 退订 → 页面路由切换安全
  • 可扩展协议版本 → 新协议只修改 SDK
  • 支持消息分发策略 → topic、filter、priority

4、一个可复用的 WebSocket Client(简化示例)

typescript 复制代码
class WSClient {
  ws?: WebSocket
  retry = 0

  connect(url: string) {
    this.ws = new WebSocket(url)

    this.ws.onopen = () => this.retry = 0

    this.ws.onclose = () => {
      setTimeout(() => this.connect(url), Math.min(2 ** this.retry * 1000, 30_000))
      this.retry++
    }
  }

  send(data: any) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data))
    }
  }
}

5、前端 WebSocket 常见翻车点(血泪)

❌ 一个页面多个 WebSocket

❌ 不关心 readyState

❌ 没有心跳

❌ 切后台连接被杀

❌ 消息没 version

五、服务端 WebSocket 架构要点

1、单机 WebSocket ≠ 可用系统

单机问题:

  • 连接数有限
  • 重启即断
  • 无法横向扩展

2、标准 WebSocket 集群架构

typescript 复制代码
Client
  ↓
LB(4 层 / 7 层)
  ↓
WS Server
  ↓
Message Broker(Redis / Kafka)

📌 关键点:

  • 连接在 WS Server
  • 消息在 Broker

3、Session 粘性(Sticky Session)

方式 是否必须
WebSocket
SSE

六、认清 WebSocket 和 SSE

1、WebSocket 和 SSE对比

  • WebSocket 是"双向实时通信通道"
  • SSE 是"服务端推送事件流"
维度 SSE WebSocket
协议基础 HTTP HTTP Upgrade → 独立协议
通信方向 单向(Server → Client) 双向(全双工)
连接类型 HTTP 长连接 TCP 长连接
自动重连 ✅ 内建 ❌ 手写
心跳 ❌ 不需要 ✅ 必须
顺序 / 断点续传 ✅ Last-Event-ID ❌ 自己实现

2、最容易选错的 6 个场景(血泪)

❌ 用 WebSocket,其实该用 SSE:

场景 正确选择
订单状态变化 SSE
支付结果回调 SSE
AI 流式输出 SSE
构建 / 导入进度 SSE
日志 / 监控流 SSE
消息通知 / 红点 SSE

✅ 必须 WebSocket 的场景:

场景 原因
IM 聊天 双向交互
实时协作 双向状态同步
游戏 / 对战 低延迟双向
设备实时控制 指令下发

七、认清「HTTP、WebSocket」和 「Socket」

1、先给你一张「总关系图」(先立世界观)

typescript 复制代码
┌─────────────┐
│   HTTP      │  ← 应用层协议
├─────────────┤
│ WebSocket   │  ← 应用层协议(可升级)
├─────────────┤
│   Socket    │  ← 通信抽象(基于 TCP / UDP)
├─────────────┤
│   TCP/UDP   │  ← 传输层
├─────────────┤
│     IP      │
└─────────────┘
  • HTTP / WebSocket 是"协议" → 【协议】
  • Socket 是"通信能力 / API 抽象"→【API】

2、HTTP 与 WebSocket 对比

对比维度 HTTP WebSocket
本质 请求-响应的应用层协议 基于 TCP 的全双工应用层协议(初始通过 HTTP Upgrade 握手)
通信模式 单向:客户端发起请求 → 服务端响应 双向全双工:客户端和服务端都可以主动发送消息
连接特性 短连接为主(HTTP/1.0 默认),HTTP/1.1 可 Keep-Alive 长连接(TCP 连接保持,直至关闭)
握手过程 TCP 三次握手 + HTTP 请求 TCP 三次握手 + HTTP Upgrade 握手 → WebSocket 协议
数据传输格式 文本:HTTP Header + Body(JSON、HTML、文件等) 帧(Frame):文本帧 / 二进制帧,开销小
服务端主动推送 ❌ 不支持(客户端必须请求) ✅ 支持,服务端可主动发送消息
延迟 较高,每次请求需发 HTTP Header 低,建立连接后数据直接发送帧
浏览器原生支持 ✅ XMLHttpRequest / Fetch / Fetch Stream ✅ WebSocket API
心跳 / 保活 依赖 TCP Keep-Alive 或轮询实现 需要手动实现心跳 / Ping-Pong
扩展性 易缓存 / 代理友好 / CDN 支持 粘性会话 + Broker / 消息总线,扩展稍复杂
典型场景 API 调用、页面资源加载、REST 接口、文件下载 实时聊天、协作编辑、IoT 控制、游戏实时交互、实时通知
优点 简单、普遍兼容、易缓存、HTTP/2 支持多路复用 双向、低延迟、轻量帧结构、实时性强
缺点 无法主动推送,开销大,高频场景不适合 服务端复杂度高,需要心跳/重连/扩展支持,浏览器每 tab 一个连接
  • HTTP = 请求-响应,适合查询 / API / 静态资源
  • WebSocket = 双向实时通道,适合低延迟、高频交互

八、WebSocket 在典型系统中的位置

1、IM 系统(时通讯系统)------人与人、人与群实时交流的系统

  • WebSocket:消息通道
  • HTTP:历史消息 / 登录

2、IoT 系统(物联网系统)------"管理、监控、控制大量设备"的系统

  • WebSocket:控制 / 实时状态
  • MQTT:设备通信

3、AI 实时系统------AI 推理结果"边算边给用户"的系统

  • SSE:流式输出
  • WebSocket:多 Agent 协作

九、WebSocket 的致命坑(真实血泪)

❌ 误区 1:连接越多越好

  • 实际:FD、内存、心跳成本爆炸

❌ 误区 2:不做 ACK

  • 实际:消息丢了你都不知道

❌ 误区 3:忽略移动端

  • 实际:后台直接断连

❌ 误区 4:用 WebSocket 做"状态同步"

  • 实际:状态错乱地狱
相关推荐
太空眼睛2 小时前
【MCP】使用SpringBoot基于Streamable-HTTP构建MCP-Client
spring boot·ai·llm·sse·mcp·mcp-client·streamable
callJJ2 小时前
WebSocket 两种实现方式对比与入门
java·python·websocket·网络协议·stomp
kaizq13 小时前
AI-MCP-SQLite-SSE本地服务及CherryStudio便捷应用
python·sqlite·llm·sse·mcp·cherry studio·fastmcp
今晚务必早点睡15 小时前
系统通信方式实战详解:HTTP、RPC、MQ、WebSocket 各用在什么场景?(附 SDK 示例)
websocket·http·rpc
太空眼睛16 小时前
【MCP】使用SpringBoot基于Streamable-HTTP构建MCP-Server
spring boot·sse·curl·mcp·mcp-server·spring-ai·streamable
松涛和鸣16 小时前
49、智能电源箱项目技术栈解析
服务器·c语言·开发语言·http·html·php
ps酷教程20 小时前
HttpPostRequestDecoder源码浅析
java·http·netty
*才华有限公司*20 小时前
RTSP视频流播放系统
java·git·websocket·网络协议·信息与通信
寻星探路21 小时前
【Python 全栈测开之路】Python 基础语法精讲(一):常量、变量与运算符
java·开发语言·c++·python·http·ai·c#