4. WebSocket 实时通信机制

在 ERP 系统中,库存变动、订单状态更新、生产报工等场景要求数据在 秒级甚至毫秒级 内从服务器推送到客户端。传统的 HTTP 轮询(Polling)存在延迟高、带宽浪费、服务器压力大等缺陷。WebSocket 协议通过建立全双工、长连接通道,从根本上解决了 ERP 实时数据推送的难题。

4.1 为什么 ERP 需要 WebSocket

对比传统 HTTP 请求与 WebSocket 在 ERP 典型场景下的差异:

对比维度 HTTP 短轮询 HTTP 长轮询 WebSocket
连接方式 每次请求新建 TCP 连接 保持连接直到超时或数据返回 一次握手,持久连接
数据推送方向 仅客户端请求后响应 模拟推送,仍需客户端发起 服务器主动推送
实时性 取决于轮询间隔(通常 3-10s) 取决于超时设置(通常 1-5s) 毫秒级
头部开销 每次请求约 800-1000 字节 每次请求约 800-1000 字节 连接建立后仅 2-6 字节帧头
ERP 典型场景 非实时报表查询 低频率消息提醒 库存变动、订单状态、设备告警
关键结论: 在 ERP 高频实时场景下,WebSocket 相比 HTTP 轮询可降低 80% 以上的网络带宽消耗,并将端到端延迟从秒级降至 100ms 以内。

4.2 WebSocket 握手流程详解

WebSocket 连接从 HTTP 升级请求开始,经过三次握手后建立全双工通道。以下是标准握手流程的五个核心步骤:

  1. 客户端发起 HTTP 升级请求: 客户端发送一个带有 Upgrade: websocket 和 Connection: Upgrade 头部的 HTTP GET 请求,同时包含 Sec-WebSocket-Key(一个随机 Base64 编码的 16 字节值)。
  2. 服务器验证并响应: 服务器接收请求后,将 Sec-WebSocket-Key 与固定 GUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11)拼接,进行 SHA-1 哈希后再 Base64 编码,生成 Sec-WebSocket-Accept 值。服务器返回 HTTP 101 Switching Protocols 状态码。
  3. 连接协议升级完成: 客户端验证 Sec-WebSocket-Accept 值正确后,TCP 连接从 HTTP 协议切换为 WebSocket 协议。
  4. 全双工数据帧传输: 双方可以随时发送数据帧,帧类型包括文本帧、二进制帧、Ping/Pong 心跳帧、关闭帧。
  5. 连接关闭: 任意一方发送关闭帧(Opcode 0x08),对方回复关闭帧确认,TCP 连接释放。
ERP 开发提示: 握手阶段必须验证 Origin 头部,防止跨站 WebSocket 劫持攻击。生产环境中建议使用 WSS(WebSocket Secure)协议,即 WebSocket over TLS,端口 443。
WebSocket 握手与数据通信流程 WebSocket 握手与数据通信流程 ERP 客户端 ERP 服务端 ① HTTP GET 升级请求 ② HTTP 101 Switching Protocols ③ 连接升级完成(全双工) ④ 双向数据帧传输 ⑤ 关闭帧 → 连接释放

4.3 ERP 实时数据推送场景

WebSocket 在 ERP 中的核心价值体现在以下三个高频场景:

场景 推送内容 典型延迟要求 推送频率
库存变动 物料入库/出库数量、库位变更、批次锁定 < 500ms 高(每次扫码/过账触发)
订单状态更新 销售订单审核、生产工单开工/完工、采购订单到货 < 1s 中(业务节点流转触发)
设备告警与工单 设备停机、温度超限、质检不合格、紧急维修派单 < 200ms 低但突发(异常事件驱动)
架构避坑: 不要将所有 ERP 数据都通过 WebSocket 推送。高频但非实时的数据(如日志归档、批量报表)仍应使用 HTTP 或消息队列异步处理。WebSocket 连接数受限于服务器内存和文件描述符,单机建议不超过 10 万并发连接。

4.4 数据帧结构与心跳机制

WebSocket 数据帧最小结构包含以下字段:

  • FIN(1 bit): 标记是否为消息的最后一个帧。
  • Opcode(4 bits): 0x1 文本帧、0x2 二进制帧、0x8 关闭帧、0x9 Ping、0xA Pong。
  • Mask(1 bit): 客户端发送给服务器的帧必须为 1(掩码处理),服务器到客户端为 0。
  • Payload Length(7/16/64 bits): 数据长度,根据大小使用不同扩展长度。
  • Masking Key(32 bits): 仅当 Mask=1 时存在,用于数据混淆。
  • Payload Data: 实际传输的应用数据。

ERP 客户端与服务端之间通常每 30-60 秒 发送一次 Ping 帧,服务端回复 Pong 帧,用于检测连接存活状态。如果连续 3 次未收到 Pong,客户端应主动重连。

4.5 客户端连接示例(JavaScript)

// ERP WebSocket 客户端连接与重连逻辑
class ERPWebSocket {
  constructor(url, options = {}) {
    this.url = url;
    this.reconnectInterval = options.reconnectInterval || 3000;
    this.maxRetries = options.maxRetries || 10;
    this.retryCount = 0;
    this.connect();
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log('ERP WebSocket 连接已建立');
      this.retryCount = 0;
      // 发送身份认证消息
      this.send({ type: 'auth', : localStorage.getItem('erp_') });
    };

    this.ws.onmessage = (event) => {
      const msg = JSON.parse(event.data);
      switch (msg.type) {
        case 'inventory_change':
          this.handleInventoryChange(msg.payload);
          break;
        case 'order_status':
          this.handleOrderStatus(msg.payload);
          break;
        case 'pong':
          // 心跳回复,无需处理
          break;
        default:
          console.warn('未知消息类型:', msg.type);
      }
    };

    this.ws.onclose = (event) => {
      console.warn('WebSocket 连接关闭, code:', event.code);
      if (this.retryCount < this.maxRetries) {
        setTimeout(() => {
          this.retryCount++;
          this.connect();
        }, this.reconnectInterval);
      }
    };

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

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

  handleInventoryChange(payload) {
    // 更新前端库存看板
    console.log('库存变动:', payload.sku, payload.newQty);
  }

  handleOrderStatus(payload) {
    // 更新订单列表状态
    console.log('订单状态:', payload.orderId, payload.status);
  }
}

// 实例化连接
const erpWs = new ERPWebSocket('wss://erp.example.com/ws');

4.6 服务端推送示例(Node.js + ws 库)

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

// 存储所有已认证的客户端连接
const clients = new Map();

wss.on('connection', (ws, req) => {
  console.log('新客户端连接, IP:', req.socket.remoteAddress);

  ws.on('message', (message) => {
    try {
      const data = JSON.parse(message);
      if (data.type === 'auth') {
        // 验证  后绑定用户会话
        const userId = verifyToken(data.);
        clients.set(userId, ws);
        ws.userId = userId;
        ws.send(JSON.stringify({ type: 'auth_ok' }));
      }
    } catch (e) {
      ws.send(JSON.stringify({ type: 'error', message: '无效消息格式' }));
    }
  });

  ws.on('close', () => {
    if (ws.userId) {
      clients.delete(ws.userId);
    }
  });

  // 发送心跳 Ping
  const heartbeat = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.ping();
    } else {
      clearInterval(heartbeat);
    }
  }, 30000);
});

// ERP 业务系统调用推送接口
function pushInventoryChange(sku, newQty, warehouseId) {
  const message = {
    type: 'inventory_change',
    payload: { sku, newQty, warehouseId, timestamp: Date.now() }
  };
  // 广播给所有相关客户端(可按仓库过滤)
  clients.forEach((ws) => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify(message));
    }
  });
}

function pushOrderStatus(orderId, status) {
  const message = {
    type: 'order_status',
    payload: { orderId, status, timestamp: Date.now() }
  };
  // 推送给特定订单负责人
  const ownerWs = clients.get(getOrderOwner(orderId));
  if (ownerWs && ownerWs.readyState === WebSocket.OPEN) {
    ownerWs.send(JSON.stringify(message));
  }
}
ERP WebSocket 实时推送架构 ERP WebSocket 实时推送架构 ERP 业务系统 库存服务 | 订单服务 | 设备监控 | 质检服务 内部事件总线 / 消息队列 WebSocket 推送服务 连接管理 | 心跳检测 | 消息路由 PC 客户端 库存看板 | 订单管理 PDA / 扫码枪 入库 | 出库 | 盘点 大屏看板 生产监控 | 告警

4.7 连接调试与常见问题

问题现象 可能原因 排查方法
握手失败(HTTP 400/403) Sec-WebSocket-Key 格式错误、Origin 被拒绝、缺少 Upgrade 头 使用浏览器开发者工具 Network 面板检查请求头;服务端日志查看拒绝原因
连接频繁断开 心跳超时、Nginx 代理超时配置过短、防火墙空闲超时 调整心跳间隔至 20-30s;检查代理层 proxy_read_timeout 配置
消息丢失或乱序 未使用消息序列号、服务端多进程广播未同步 在消息体中增加 seq 字段;使用 Redis Pub/Sub 同步多节点推送
WSS 连接证书错误 自签名证书、证书域名不匹配、中间证书缺失 使用受信任 CA 证书;检查证书链完整性
调试工具推荐: 使用 Chrome DevTools 的 Network 面板筛选 WS 协议,可以实时查看 WebSocket 帧内容。服务端可使用 wscat 命令行工具模拟客户端连接进行快速验证。