Skip to content

实时状态同步

本文档引用文件

目录

  1. 项目结构
  2. 核心组件
  3. 架构概览
  4. 详细组件分析
  5. 依赖分析
  6. 性能考虑
  7. 故障排除指南

项目结构

本项目包含多个子模块,主要分为 codegendenopcuni 四个部分。其中:

  • codegen:代码生成工具及相关配置。
  • deno:后端服务,使用 Deno 构建,包含 WebSocket 服务逻辑。
  • pc:PC 端前端应用,基于 Vue 框架实现。
  • uni:移动端跨平台应用,使用 UniApp 框架开发。

WebSocket 实时通信功能在 pc/src/compositions/websocket.tsuni/src/compositions/websocket.ts 中实现,服务端逻辑位于 deno/lib/websocket/ 目录下。

图示来源

核心组件

实时状态同步的核心是 WebSocket 通信机制,通过组合式函数封装连接管理、消息订阅与发布、自动重连等功能。客户端通过 useSubscribesubscribepublish 等 API 与服务端交互,服务端基于路由和数据访问对象(DAO)处理连接与消息转发。

组件职责:

  • 客户端:建立连接、心跳维持、订阅/取消订阅主题、接收并分发消息。
  • 服务端:验证连接、管理客户端会话、维护主题订阅关系、广播消息。

本节来源

架构概览

系统采用典型的客户端-服务器架构,通过 WebSocket 协议实现实时双向通信。客户端在初始化时创建唯一 clientId,并通过认证参数连接服务端。服务端验证密码后升级为 WebSocket 连接,并维护客户端与订阅主题的映射关系。

图示来源

详细组件分析

客户端连接管理

客户端通过 connect() 函数建立 WebSocket 连接。连接地址根据协议(HTTP/HTTPS)自动选择 ws://wss://,并携带认证密码和客户端唯一标识。

typescript
const isSSL = location.protocol === 'https:';
const url = (isSSL ? 'wss://' : 'ws://') + location.host + '/api/websocket/upgrade?pwd='+ PWD + '&clientId=' + encodeURIComponent(clientId);

在移动端(uni-app),使用 uni.connectSocket 创建连接:

typescript
socket = uni.connectSocket({
  url,
  success() {},
  fail() {
    socket = undefined;
  },
});

本节来源

消息处理与回调分发

客户端收到消息后,解析 JSON 数据,提取 topicpayload,并从 topicCallbackMap 中查找对应的回调函数执行。

typescript
socket.onmessage = function(event) {
  const eventData = event.data;
  if (typeof eventData !== "string") return;
  if (eventData === "pong") return;
  const obj = JSON.parse(eventData as string);
  const topic = obj.topic;
  const payload = obj.payload;
  const callbacks = topicCallbackMap.get(topic);
  if (callbacks && callbacks.length > 0) {
    for (const callback of callbacks) {
      callback(payload);
    }
  }
};

本节来源

自动重连机制

当连接关闭或出错时,触发 reConnect() 函数。采用指数退避策略,前10次尝试以 reConnectNum * 200ms 间隔重连,超过10次则固定为5秒。

typescript
async function reConnect() {
  if (socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)) {
    return socket;
  }
  if (topicCallbackMap.size === 0) {
    return;
  }
  reConnectNum++;
  let time = 200;
  if (reConnectNum > 10) {
    time = 5000;
  } else {
    time = reConnectNum * 200;
  }
  await new Promise((resolve) => setTimeout(resolve, time));
  await connect();
}

本节来源

订阅与发布机制

客户端通过 subscribe(topic, callback) 注册主题监听,服务端将主题记录在 clientIdTopicsMap 中。发布消息时,服务端遍历所有订阅该主题的客户端并发送消息。

图示来源

心跳机制

客户端每60秒发送一次 ping 消息,服务端收到后回复 pong,防止连接因超时被关闭。

typescript
function socketPing() {
  if (socket && socket.readyState === WebSocket.OPEN) {
    socket.send("ping");
  }
  setTimeout(socketPing, 60000);
}
socketPing();

服务端处理:

typescript
if (eventData === "ping") {
  socket.send("pong");
  return;
}

本节来源

依赖分析

系统依赖关系清晰,前端通过 WebSocket 与后端通信,后端内部通过常量模块共享状态。

图示来源

性能考虑

  • 连接复用:同一 clientId 的新连接会关闭旧连接,避免资源浪费。
  • 批量订阅:支持一次订阅多个主题,减少网络请求。
  • 内存管理:无订阅时10分钟后自动关闭连接,防止内存泄漏。
  • 消息节流:心跳间隔60秒,平衡保活与性能。
  • 错误容忍:网络异常自动重连,提升用户体验。

故障排除指南

问题可能原因解决方案
连接失败密码错误或缺少 clientId检查 PWDclientId 参数
消息未收到主题未正确订阅确认 subscribe 已调用且主题名一致
频繁重连网络不稳定或服务端异常检查网络,查看服务端日志
内存泄漏未正确取消订阅使用 unSubscribe 清理回调,或使用 useSubscribe 自动管理生命周期

本节来源