React Native 项目中 WebSocket 的完整实现方案【完善版】

简介

在现代移动应用开发中,实时通信已成为不可或缺的功能。无论是聊天应用、在线游戏、物联网监控,还是协作工具,都需要实时、双向的数据传输能力。WebSocket 作为 HTML5 规范的一部分,提供了全双工通信能力,特别适合需要实时数据交换的场景。

本文基于一个实际的 React Native 机器人应用项目,深入探讨 WebSocket 的原理、封装设计和使用实践。这个项目需要实时接收任务指令、发送状态更新、处理健康数据等。通过 WebSocket 实现,系统能够提供流畅的实时交互体验。

在开发过程中,遇到了连接稳定性、消息处理、错误恢复、性能优化等多个挑战。通过系统性的架构设计和精细的实现,最终构建了一个稳定、高效、易维护的 WebSocket 解决方案。本文将详细分享这些经验和最佳实践,希望能帮助更多的开发者构建出优秀的实时通信功能!


WebSocket 基础原理

什么是 WebSocket?

WebSocket 是一种网络通信协议,它允许客户端和服务器之间建立持久的双向连接。与传统的 HTTP 请求-响应模式不同,WebSocket 连接一旦建立,双方都可以主动发送数据,无需等待对方的请求。

WebSocket 协议最初是为了解决 Web 应用中实时通信的需求而设计的。在 WebSocket 出现之前,开发者通常使用轮询(Polling)或长轮询(Long Polling)来实现实时通信,但这些方法效率低下、资源浪费。

WebSocket 通过一次握手建立连接后,就可以进行全双工通信,大大提高了通信效率。它使用与 HTTP 相同的端口(80/443),但协议完全不同,这使得它能够穿透大多数防火墙和代理服务器。

WebSocket 的优势

  1. 低延迟:无需重复建立连接,数据传输延迟极低

    • 传统 HTTP 每次请求都需要建立 TCP 连接,而 WebSocket 连接建立后可以持续使用
    • 避免了 TCP 三次握手的开销,数据传输更加迅速
  2. 全双工通信:客户端和服务器可以同时发送和接收数据

    • 不同于 HTTP 的请求-响应模式,WebSocket 允许任意一方主动发送数据
    • 特别适合需要实时交互的场景,如聊天、游戏、协作编辑等
  3. 减少带宽消耗:避免了 HTTP 请求的头部开销

    • HTTP 请求每次都需要携带完整的头部信息,而 WebSocket 只在握手时发送头部
    • 对于频繁通信的场景,可以显著减少网络带宽消耗
  4. 实时性强:适合聊天、游戏、实时监控等场景

    • 消息可以立即传递,无需等待客户端轮询
    • 支持服务器主动推送,用户体验更佳
  5. 协议简单:相比其他实时通信协议,WebSocket 协议相对简单

    • 易于实现和理解
    • 浏览器原生支持,无需额外插件

WebSocket 连接生命周期

WebSocket 连接的建立过程如下:

客户端                    服务器
  |                        |
  |---- HTTP Upgrade ----->|  (1) 客户端请求升级为WebSocket
  |<--- 101 Switching -----|  (2) 服务器同意升级
  |                        |
  |<==== 双向数据传输 ====>|  (3) 开始双向数据传输
  |                        |
  |---- Close Frame ------>|  (4) 客户端关闭连接
  |<--- Close Frame -------|  (5) 服务器关闭连接

详细说明:

  1. 握手阶段:客户端发送 HTTP 升级请求,包含 Upgrade: websocketConnection: Upgrade 头部
  2. 协议切换:服务器返回 101 状态码,表示同意升级到 WebSocket 协议
  3. 数据传输:连接建立后,双方可以发送文本或二进制数据
  4. 连接关闭:任一方都可以发送关闭帧来终止连接

WebSocket 消息格式

WebSocket 支持两种消息格式:

  • 文本消息:UTF-8 编码的字符串,适合传输 JSON、XML 等文本数据
  • 二进制消息:原始字节数据,适合传输图片、音频、视频等二进制内容

在项目中,主要使用 JSON 格式的文本消息进行通信,这样便于调试和扩展。


项目架构设计

整体架构图

WebSocket 实现采用了分层架构设计,每一层都有明确的职责和边界。这种设计每一层都有其独特的功能,但又相互配合,共同构建出一个稳固而优雅的系统。

┌─────────────────────────────────────────────────────────────┐
│                    React Native App                         │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────┐  ┌─────────────────┐  ┌──────────────┐ │
│  │   UI Components  │  │  Business Logic  │  │   Services   │ │
│  │                 │  │                 │  │              │ │
│  │ • NotifyScreen  │  │ • Message       │  │ • API Calls  │ │
│  │ • StatusDisplay │  │   Processing    │  │ • Data Sync  │ │
│  │ • TaskPanels    │  │ • State Mgmt    │  │ • Auth       │ │
│  └─────────────────┘  └─────────────────┘  └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────────┐ │
│  │              WebSocket Context Layer                   │ │
│  │  ┌─────────────────┐  ┌─────────────────┐              │ │
│  │  │ WebSocketProvider│  │ WebSocketStatus │              │ │
│  │  │                 │  │                 │              │ │
│  │  │ • Global State  │  │ • Connection    │              │ │
│  │  │ • Message Mgmt  │  │   Status        │              │ │
│  │  │ • Event Bus     │  │ • Error Display │              │ │
│  │  └─────────────────┘  └─────────────────┘              │ │
│  └─────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────────┐ │
│  │                WebSocket Hooks Layer                    │ │
│  │  ┌─────────────────┐  ┌─────────────────┐              │ │
│  │  │  useWebSocket    │  │useWebSocketWithUser│           │ │
│  │  │                 │  │                 │              │ │
│  │  │ • Core Logic    │  │ • User          │              │ │
│  │  │ • Connection    │  │   Integration   │              │ │
│  │  │ • Reconnection  │  │ • Auto Connect │              │ │
│  │  │ • Heartbeat     │  │ • State Sync   │              │ │
│  │  └─────────────────┘  └─────────────────┘              │ │
│  └─────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────────┐ │
│  │                 Utility Layer                           │ │
│  │  ┌─────────────────┐  ┌─────────────────┐              │ │
│  │  │ webSocketGuard  │  │   Environment   │              │ │
│  │  │                 │  │                 │              │ │
│  │  │ • Validation    │  │ • Platform      │              │ │
│  │  │ • Config Build  │  │   Detection     │              │ │
│  │  │ • Connection    │  │ • Debug Mode    │              │ │
│  │  │   Guards        │  │ • URL Config    │              │ │
│  │  └─────────────────┘  └─────────────────┘              │ │
│  └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

设计原则

  1. 分层架构:将 WebSocket 功能分为工具层、Hook 层、Context 层和组件层

    • 工具层:提供基础的工具函数和配置管理
    • Hook 层:封装核心的 WebSocket 逻辑,提供可复用的状态和方法
    • Context 层:管理全局状态,提供跨组件的状态共享
    • 组件层:实现具体的 UI 组件和业务逻辑
  2. 职责分离:每个层次都有明确的职责,便于维护和测试

    • 每一层只关注自己的核心功能,不越界处理其他层的逻辑
    • 通过接口定义层与层之间的交互,降低耦合度
    • 便于单独测试每一层的功能
  3. 可复用性:核心逻辑封装在 Hook 中,可在不同组件中复用

    • Hook 提供标准化的接口,可以在任何组件中使用
    • 业务逻辑与 UI 逻辑分离,提高代码复用率
    • 支持不同的配置参数,适应不同的使用场景
  4. 类型安全:使用 TypeScript 提供完整的类型定义

    • 所有接口和数据结构都有明确的类型定义
    • 编译时类型检查,减少运行时错误
    • 提供良好的 IDE 支持和代码提示

架构优势

这种分层架构设计带来了以下优势:

  • 可维护性:每层职责清晰,修改影响范围可控
  • 可测试性:可以单独测试每一层的功能
  • 可扩展性:新增功能时只需要在相应层添加代码
  • 可复用性:底层逻辑可以在不同场景中复用
  • 团队协作:不同开发者可以专注于不同层的开发

流程图模块

为了更好地理解 WebSocket 系统的工作流程,下面提供了几个关键流程的详细流程图。这些流程图展示了从连接建立到消息处理的完整过程,帮助开发者快速理解系统的运行机制。

1. WebSocket 连接建立流程

这个流程图展示了从发起连接到成功建立连接的完整过程,包括初始心跳验证机制。

已连接
未连接
5秒内收到响应
5秒超时
开始连接
检查连接状态
跳过连接
设置连接状态为 connecting
构建 WebSocket URL
创建 WebSocket 实例
监听 onopen 事件
发送初始心跳消息
msgType: 'hb'
设置5秒超时定时器
等待服务端响应
收到心跳响应
连接验证失败
设置连接状态为 connected
启动定期心跳检测
处理待发送消息队列
触发 onOpen 回调
连接建立成功
设置连接状态为 error
关闭连接
触发重连机制

2. 消息发送流程(带 Ping 检测)

这个流程图展示了发送消息时的完整流程,包括 Ping 检测和消息队列机制。

连接未打开
连接已打开
5秒内收到响应
5秒超时
调用 sendMessage
执行 Ping 检测
检查连接状态
Ping 失败
发送心跳消息
msgType: 'hb'
设置5秒超时
等待服务端响应
Ping 成功
直接发送消息
是否记录历史
记录到消息历史
仅发送不记录
消息发送成功
将消息加入队列
停止心跳检测
关闭当前连接
触发重连机制
等待连接恢复
连接恢复
处理消息队列
继续等待
逐条发送队列消息

3. 心跳检测流程

这个流程图展示了定期心跳检测的完整流程,包括心跳发送和响应检测。

连接未打开
连接已打开
收到心跳响应
5秒超时未收到响应
心跳定时器触发
检查 WebSocket 状态
停止心跳检测
发送心跳消息
msgType: 'hb'
记录发送时间
设置响应标志为 false
设置5秒响应超时
等待服务端响应
设置响应标志为 true
心跳检测失败
清除响应超时定时器
记录响应时间
等待下次心跳周期
关闭连接
触发重连机制
清理定时器

4. 重连机制流程

这个流程图展示了连接断开后的自动重连机制,包括重连次数限制和状态管理。

主动关闭 code=1000
异常断开
正在重连中
未在重连
达到最大次数
未达到最大次数
连接成功
连接失败
连接断开
检查断开原因
不触发重连
检查重连标志
跳过重复调用
检查重连次数
停止重连
设置重连标志
更新重连次数
设置连接状态为 reconnecting
延迟重连
默认3秒
构建 WebSocket URL
创建新的 WebSocket 实例
监听连接事件
连接结果
重置重连次数
继续重连流程
设置连接状态为 connected
发送初始心跳验证
连接建立成功
设置连接状态为 error
触发 onReconnectFailed 回调
显示连接失败弹窗

5. 消息接收和处理流程

这个流程图展示了接收和处理 WebSocket 消息的完整流程。

字符串
二进制
成功
失败
收到 WebSocket 消息
解析消息数据
消息类型
尝试 JSON 解析
直接使用二进制数据
解析结果
使用解析后的对象
使用原始字符串
检查消息类型
是否为心跳响应
msgType: 'hb'
是否为初始心跳
添加到消息历史
确认连接成功
标记心跳响应
启动定期心跳
处理消息队列
触发 onOpen 回调
清除响应超时
触发 onMessage 回调
通知消息监听器
解析周期任务事件
是否为周期任务
添加到周期事件列表
根据 msgType 处理
触发全局周期任务提示
处理业务逻辑
消息处理完成

6. 用户集成自动连接流程

这个流程图展示了 useWebSocketWithUser Hook 的自动连接逻辑。

false
true
未登录
已登录
无 robotId
有 robotId
robotId 未同步
robotId 已同步
已连接
正在连接
正在重连
未连接
达到最大次数
未达到最大次数
组件初始化
获取用户信息
检查 autoConnect 配置
不自动连接
检查用户登录状态
等待用户登录
检查 robotId
跳过连接
未绑定机器人
检查配置同步
等待配置同步
检查连接状态
跳过连接
检查重连次数
不再自动连接
调用 connect
建立 WebSocket 连接
用户登录
保持等待
配置更新

7. 整体系统工作流程

这个流程图展示了从应用启动到消息处理的完整系统流程。

成功
失败
应用启动
初始化 WebSocketProvider
获取用户信息
用户已登录且有 robotId
自动建立连接
等待用户登录
WebSocket 连接建立
发送初始心跳
收到心跳响应
连接验证成功
启动定期心跳
系统就绪
应用运行中
接收消息
发送消息
解析消息
处理业务逻辑
Ping 检测
Ping 结果
发送消息
加入消息队列
触发重连
重连成功
处理消息队列
继续重连
用户登录

核心封装实现

1. 基础 WebSocket Hook (useWebSocket)

这是整个 WebSocket 功能的核心。该 Hook 封装了 WebSocket 的原生 API,提供了更高级的功能和更好的开发体验。通过这个 Hook,可以轻松地在任何 React 组件中使用 WebSocket 功能,而无需关心底层的连接管理细节。

接口定义

// WebSocket 配置接口,定义了连接所需的所有参数
export interface WebSocketConfig {
  url: string;                    // WebSocket 服务器地址
  robotId?: string;              // 机器人 ID,用于身份识别
  protocols?: string | string[]; // WebSocket 子协议
  reconnectInterval?: number;    // 重连间隔时间(毫秒)
  maxReconnectAttempts?: number; // 最大重连次数
  heartbeatInterval?: number;   // 心跳间隔时间(毫秒)
  heartbeatMessage?: string;     // 心跳消息内容(已废弃,使用应用层心跳 { msgType: 'hb' })
  debug?: boolean;              // 是否开启调试模式
}

// WebSocket Hook 选项接口
export interface UseWebSocketOptions {
  config: WebSocketConfig;
  onMessage?: (message: WebSocketMessage) => void;
  onOpen?: () => void;
  onClose?: (event: any) => void;
  onError?: (error: Event) => void;
  onReconnect?: (attempt: number) => void;
  onReconnectFailed?: () => void;
  onConnectionFailed?: () => void; // 连接失败回调(用于弹窗提示)
}

// Hook 返回值接口,定义了外部可以使用的状态和方法
export interface UseWebSocketReturn {
  // 连接状态相关
  isConnected: boolean;          // 是否已连接
  isConnecting: boolean;         // 是否正在连接
  isReconnecting: boolean;       // 是否正在重连
  connectionState: 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error';

  // 消息历史相关
  messages: WebSocketMessage[];  // 消息历史记录
  messageCount: number;          // 消息总数

  // 连接信息相关
  url: string;                   // 完整的 WebSocket URL
  lastError: string | null;      // 最后一次错误信息
  reconnectAttempts: number;     // 当前重连次数

  // 操作方法
  connect: () => void;           // 建立连接
  disconnect: () => void;        // 断开连接
  sendMessage: (data: any, type?: 'text' | 'json') => void;        // 发送消息(记录历史)
  sendMessageNoTrack: (data: any, type?: 'text' | 'json') => void; // 发送消息(不记录历史)
  clearMessages: () => void;     // 清空消息历史

  // 调试方法
  getDebugInfo: () => object;    // 获取调试信息
}

核心特性详解

1. 智能重连机制

在实际应用中,网络连接可能会因为各种原因断开。重连机制能够自动检测连接断开并尝试重新连接。

const attemptReconnect = useCallback(() => {
  const maxAttempts = config.maxReconnectAttempts || 5;  // 🎯 最大重连次数
  const interval = config.reconnectInterval || 3000;      // ⏰ 重连间隔

  // 🔍 检查是否应该停止重连
  if (shouldStopReconnecting.current) {
    console.log('🔗 [WebSocket] 已达到最大重连次数,停止重连操作');
    return;
  }

  // 🚫 防止重复执行重连逻辑
  if (isReconnectingRef.current) {
    console.log('🔗 [WebSocket] 正在重连中,跳过重复调用');
    return;
  }

  // 📊 检查是否已经达到最大重连次数
  if (reconnectAttempts >= maxAttempts) {
    console.error('🔗 [WebSocket] 重连失败,已达到最大重连次数');
    setConnectionState('error');
    setIsReconnecting(false);
    isReconnectingRef.current = false;
    shouldStopReconnecting.current = true;

    // 💬 只弹窗提示一次,避免重复提示
    if (!hasShownMaxReconnectAlert.current) {
      hasShownMaxReconnectAlert.current = true;
      onReconnectFailed?.();
    }
    return;
  }

  // 🎯 设置重连标志,防止并发重连
  isReconnectingRef.current = true;
  console.log('🔗 [WebSocket] 开始重连流程,设置重连标志');

  // 🔄 更新重连次数并延迟执行重连
  setReconnectAttempts((prev) => {
    const newAttempts = prev + 1;
  
    if (newAttempts > maxAttempts) {
      // 🛑 如果超过最大次数,停止重连
      setConnectionState('error');
      setIsReconnecting(false);
      isReconnectingRef.current = false;
      shouldStopReconnecting.current = true;
      return prev;
    }

    setIsReconnecting(true);
    setConnectionState('reconnecting');

    console.log(`🔗 [WebSocket] 尝试重连 (${newAttempts}/${maxAttempts})`);
    onReconnect?.(newAttempts);

    // ⏰ 延迟重连,避免立即重连造成服务器压力
    reconnectTimeoutRef.current = setTimeout(() => {
      // 🔧 重连逻辑实现...
    }, interval);

    return newAttempts;
  });
}, [config, reconnectAttempts, onReconnect, onReconnectFailed]);

重连机制的优势:

  • 指数退避:重连间隔可以逐渐增加,避免对服务器造成压力
  • 最大次数限制:防止无限重连,避免资源浪费
  • 状态管理:准确跟踪重连状态,提供用户反馈
  • 并发控制:防止多个重连请求同时执行

2. 心跳保活机制

心跳机制是保持 WebSocket 连接活跃的重要手段。在网络环境不稳定的情况下,连接可能会因为长时间没有数据传输而被中间设备(如 NAT、防火墙)关闭。

心跳机制采用了应用层心跳消息格式,使用 { msgType: 'hb' } 作为心跳消息,并实现了心跳响应检测机制,确保连接的真实有效性。

const startHeartbeat = useCallback(() => {
  console.log('🔗 [WebSocket] startHeartbeat 被调用');
  
  // 先停止之前的心跳(如果存在)
  if (heartbeatTimeoutRef.current) {
    console.log('🔗 [WebSocket] 停止之前的心跳检测');
    clearInterval(heartbeatTimeoutRef.current);
    heartbeatTimeoutRef.current = null;
  }

  if (!config.heartbeatInterval) {
    console.warn('🔗 [WebSocket] 心跳间隔未配置,无法启动心跳检测');
    return;
  }

  // 直接检查 WebSocket 的实际状态,而不是依赖 isConnected 状态(避免闭包陷阱)
  if (!wsRef.current) {
    console.warn('🔗 [WebSocket] WebSocket 实例不存在,无法启动心跳检测');
    return;
  }

  const currentState = wsRef.current.readyState;
  if (currentState !== WebSocket.OPEN) {
    console.warn(`🔗 [WebSocket] WebSocket 连接未打开 (状态: ${currentState}),无法启动心跳检测`);
    return;
  }

  const sendHeartbeat = () => {
    const now = formatLocalTime(new Date());
    console.log(`🔗 [WebSocket] 心跳检测定时器触发: ${now}`);
    
    if (!wsRef.current) {
      console.warn('🔗 [WebSocket] WebSocket 实例不存在,停止心跳检测');
      if (heartbeatTimeoutRef.current) {
        clearInterval(heartbeatTimeoutRef.current);
        heartbeatTimeoutRef.current = null;
      }
      return;
    }

    const currentState = wsRef.current.readyState;
    if (currentState === WebSocket.OPEN) {
      try {
        // 发送应用层心跳消息 { msgType: 'hb' }
        const heartbeatMsg = { msgType: 'hb' };
        wsRef.current.send(JSON.stringify(heartbeatMsg));
        lastHeartbeatTimeRef.current = Date.now();
        lastHeartbeatResponseRef.current = false;
        console.log(`🔗 [WebSocket] 发送心跳成功: ${JSON.stringify(heartbeatMsg)}, 时间: ${formatLocalTime(new Date())}`);

        // 设置心跳响应超时检测(5秒内未收到响应则判定为连接失败)
        if (heartbeatResponseTimeoutRef.current) {
          clearTimeout(heartbeatResponseTimeoutRef.current);
        }
        heartbeatResponseTimeoutRef.current = setTimeout(() => {
          if (!lastHeartbeatResponseRef.current) {
            console.error('🔗 [WebSocket] 心跳检测失败: 未收到服务端响应,触发重连');
            // 判定为连接失败,触发重连
            if (wsRef.current) {
              wsRef.current.close(); // 关闭连接,触发 onclose 事件,从而触发重连
            }
          }
        }, 5000); // 5秒超时
      } catch (error) {
        console.error('🔗 [WebSocket] 发送心跳失败:', error);
        // 发送失败,触发重连
        if (wsRef.current) {
          wsRef.current.close();
        }
      }
    } else {
      // 如果连接已断开,停止心跳
      console.warn(`🔗 [WebSocket] 连接已断开 (状态: ${currentState}),停止心跳检测`);
      if (heartbeatTimeoutRef.current) {
        clearInterval(heartbeatTimeoutRef.current);
        heartbeatTimeoutRef.current = null;
      }
    }
  };

  // 立即执行一次心跳(用于测试)
  console.log('🔗 [WebSocket] 立即执行第一次心跳测试');
  sendHeartbeat();

  heartbeatTimeoutRef.current = setInterval(sendHeartbeat, config.heartbeatInterval);
  console.log(`🔗 [WebSocket] 心跳检测已启动,间隔: ${config.heartbeatInterval}ms (${config.heartbeatInterval / 1000}秒)`);
}, [config.heartbeatInterval, config.heartbeatMessage, config.debug]);

心跳机制的优势:

  • 连接保活:定期发送数据,保持连接活跃
  • 故障检测:通过心跳响应检测连接是否正常
  • 响应超时检测:5秒内未收到心跳响应则判定为连接失败,自动触发重连
  • 应用层心跳:使用结构化的 JSON 消息格式,便于服务端识别和处理
  • 资源清理:连接断开时自动清理定时器
  • 可配置:心跳间隔和消息内容都可以配置

3. Ping 检测机制

在发送消息前,会先进行 ping 检测,确保连接的真实有效性。

// Ping 检测连接是否成功(发送 ping 并等待服务端响应)
const pingConnection = useCallback((): Promise<boolean> => {
  return new Promise((resolve) => {
    // 如果正在 ping,等待上一次完成
    if (isPingingRef.current) {
      setTimeout(() => {
        resolve(pingConnection());
      }, 100);
      return;
    }

    isPingingRef.current = true;

    // 检查 WebSocket 实例和状态
    if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
      isPingingRef.current = false;
      resolve(false);
      return;
    }

    const ws = wsRef.current;
    const pingTimeout = 5000; // 5秒超时
    let pingResolved = false;

    // 监听服务端响应
    const originalOnMessage = ws.onmessage;
    let timeoutId: number | null = null;
    
    // 消息处理器(用于检测心跳响应)
    const pingResponseHandler = (event: any) => {
      try {
        let messageData: any;
        if (typeof event.data === 'string') {
          try {
            messageData = JSON.parse(event.data);
          } catch {
            messageData = event.data;
          }
        } else {
          messageData = event.data;
        }

        // 检测是否是心跳响应(msgType 为 'hb')
        const isHeartbeatResponse = 
          (typeof messageData === 'object' && messageData !== null && messageData.msgType === 'hb');

        if (isHeartbeatResponse && !pingResolved) {
          pingResolved = true;
          if (timeoutId) {
            clearTimeout(timeoutId);
            timeoutId = null;
          }
          isPingingRef.current = false;
          // 恢复原始消息处理器
          if (ws.onmessage === pingResponseHandler) {
            ws.onmessage = originalOnMessage;
          }
          console.log(`🔗 [WebSocket] Ping 检测成功: 已发送心跳消息且收到服务端响应`, messageData);
          resolve(true);
          return;
        }

        // 如果不是心跳响应,继续传递给原始处理器
        if (originalOnMessage && !pingResolved) {
          originalOnMessage(event);
        }
      } catch (error) {
        console.error('🔗 [WebSocket] Ping 检测处理响应失败:', error);
      }
    };

    // 设置超时
    timeoutId = setTimeout(() => {
      if (!pingResolved) {
        pingResolved = true;
        isPingingRef.current = false;
        if (ws.onmessage === pingResponseHandler) {
          ws.onmessage = originalOnMessage;
        }
        console.warn('🔗 [WebSocket] Ping 检测超时: 未收到服务端响应,判定为连接失败');
        resolve(false);
      }
    }, pingTimeout) as any;

    // 临时替换消息处理器
    ws.onmessage = pingResponseHandler;

    try {
      // 发送应用层心跳消息 { msgType: 'hb' }
      const pingMsg = { msgType: 'hb' };
      ws.send(JSON.stringify(pingMsg));
      console.log(`🔗 [WebSocket] Ping 检测: 已发送心跳消息 ${JSON.stringify(pingMsg)},等待服务端响应(超时: ${pingTimeout}ms)`);
    } catch (error) {
      if (timeoutId) {
        clearTimeout(timeoutId);
        timeoutId = null;
      }
      if (ws.onmessage === pingResponseHandler) {
        ws.onmessage = originalOnMessage;
      }
      isPingingRef.current = false;
      console.error('🔗 [WebSocket] Ping 检测失败: 发送心跳消息失败', error);
      resolve(false);
    }
  });
}, []);

Ping 检测机制的优势:

  • 连接验证:在发送消息前验证连接的真实有效性,避免发送到无效连接
  • 超时检测:5秒内未收到响应则判定为连接失败
  • 自动重连:Ping 失败时自动触发重连机制
  • 消息队列:Ping 失败时,消息会被加入队列,等待连接恢复后发送

4. 消息队列机制

当 ping 检测失败时,消息会被加入队列,等待连接恢复后自动发送,确保消息不会丢失。

// 消息队列:存储待发送的消息(ping 失败时)
interface PendingMessage {
  data: any;
  type: 'text' | 'json';
  track: boolean; // 是否记录到消息历史
}
const pendingMessagesRef = useRef<PendingMessage[]>([]);

// 处理待发送的消息队列
const processPendingMessages = useCallback(async () => {
  if (pendingMessagesRef.current.length === 0) return;

  const messages = [...pendingMessagesRef.current];
  pendingMessagesRef.current = []; // 清空队列

  if (config.debug) {
    console.log(`🔗 [WebSocket] 处理 ${messages.length} 条待发送消息`);
  }

  // 逐条发送队列中的消息
  for (const msg of messages) {
    if (msg.track) {
      await sendMessageInternal(msg.data, msg.type, true);
    } else {
      await sendMessageInternal(msg.data, msg.type, false);
    }
  }
}, [config.debug, sendMessageInternal]);

消息队列机制的优势:

  • 消息暂存:连接失败时暂存消息,避免消息丢失
  • 自动恢复:连接恢复后自动发送队列中的消息
  • 分类管理:区分是否记录历史的消息,灵活处理
  • 自动清理:达到最大重连次数时自动清空队列

5. 初始心跳验证机制

连接建立后,会发送初始心跳消息并等待服务端响应,只有收到响应才认为连接真正成功。

// 连接打开
wsRef.current.onopen = () => {
  console.log('🔗 [WebSocket] WebSocket 连接已打开,发送初始心跳消息验证连接');
  setIsConnecting(false);
  setIsReconnecting(false);
  setReconnectAttempts(0);
  initialHeartbeatResponseRef.current = false;

  // 发送初始心跳消息验证连接
  try {
    if (!wsRef.current) {
      console.error('🔗 [WebSocket] WebSocket 实例不存在,无法发送初始心跳消息');
      return;
    }
    const initialHeartbeatMsg = { msgType: 'hb' };
    wsRef.current.send(JSON.stringify(initialHeartbeatMsg));
    console.log(`🔗 [WebSocket] 已发送初始心跳消息: ${JSON.stringify(initialHeartbeatMsg)},等待服务端响应以确认连接成功`);

    // 设置超时:5秒内未收到响应则判定为连接失败
    if (initialHeartbeatTimeoutRef.current) {
      clearTimeout(initialHeartbeatTimeoutRef.current);
    }
    initialHeartbeatTimeoutRef.current = setTimeout(() => {
      if (!initialHeartbeatResponseRef.current) {
        console.error('🔗 [WebSocket] 初始心跳超时: 未收到服务端响应,判定为连接失败');
        setConnectionState('error');
        setIsConnected(false);
        setLastError('连接验证失败: 未收到心跳响应');
        if (wsRef.current) {
          wsRef.current.close();
        }
      }
    }, 5000) as any;
  } catch (error) {
    console.error('🔗 [WebSocket] 发送初始心跳消息失败:', error);
    setConnectionState('error');
    setIsConnected(false);
    setLastError(`连接验证失败: ${error}`);
    if (wsRef.current) {
      wsRef.current.close();
    }
  }
};

// 接收消息
wsRef.current.onmessage = (event) => {
  // ... 解析消息 ...
  
  // 检测心跳响应(msgType 为 'hb')
  const isHeartbeatResponse = 
    (typeof messageData === 'object' && messageData !== null && messageData.msgType === 'hb');
  
  if (isHeartbeatResponse) {
    // 如果是初始连接建立时的心跳响应,确认连接成功
    if (!initialHeartbeatResponseRef.current) {
      initialHeartbeatResponseRef.current = true;
      if (initialHeartbeatTimeoutRef.current) {
        clearTimeout(initialHeartbeatTimeoutRef.current);
        initialHeartbeatTimeoutRef.current = null;
      }
      console.log('🔗 [WebSocket] 收到初始心跳响应,连接验证成功,连接已建立');
      setIsConnected(true);
      setConnectionState('connected');
      
      // 启动心跳
      startHeartbeat();

      // 处理待发送的消息队列
      processPendingMessages();

      // 触发 onOpen 回调
      onOpen?.();
    }

    // 标记心跳响应(用于定期心跳检测)
    lastHeartbeatResponseRef.current = true;
    if (heartbeatResponseTimeoutRef.current) {
      clearTimeout(heartbeatResponseTimeoutRef.current);
      heartbeatResponseTimeoutRef.current = null;
    }
    console.log(`🔗 [WebSocket] 心跳检测收到服务端响应:`, messageData, `时间: ${formatLocalTime(new Date())}`);
  }
  
  // ... 处理其他消息 ...
};

初始心跳验证机制的优势:

  • 连接验证:确保连接真正建立,而不仅仅是 TCP 连接成功
  • 超时检测:5秒内未收到响应则判定为连接失败
  • 自动启动:验证成功后自动启动心跳和消息队列处理
  • 错误处理:验证失败时自动关闭连接并触发重连

6. 发送消息机制(带 Ping 检测)

在发送消息前,会先进行 ping 检测,确保连接的真实有效性。如果 ping 失败,消息会被加入队列,等待连接恢复后自动发送。

// 发送消息(带 ping 检测)
const sendMessage = useCallback(
  async (data: any, type: 'text' | 'json' = 'text') => {
    // 先进行 ping 检测
    const isConnected = await pingConnection();

    if (!isConnected) {
      // Ping 失败,将消息加入队列并触发重连
      console.warn('🔗 [WebSocket] Ping 检测失败,消息加入队列,触发重连');
      pendingMessagesRef.current.push({ data, type, track: true });

      // 关闭当前连接(如果不是正常关闭),触发重连
      if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
        console.log('🔗 [WebSocket] Ping 检测失败,关闭连接以触发重连');
        try {
          // 先停止心跳,避免干扰
          stopHeartbeat();
          // 关闭连接,使用非正常关闭码,确保触发重连
          wsRef.current.close(1006, 'Ping检测失败');
          // 延迟一点时间,确保 onclose 事件能触发,如果还没触发则直接调用重连
          setTimeout(() => {
            if (!isReconnectingRef.current && !isConnecting) {
              console.log('🔗 [WebSocket] Ping 检测失败,延迟后直接触发重连');
              attemptReconnect();
            }
          }, 100);
        } catch (error) {
          console.error('🔗 [WebSocket] 关闭连接失败:', error);
          // 如果关闭失败,直接触发重连
          if (!isReconnectingRef.current && !isConnecting) {
            console.log('🔗 [WebSocket] 关闭连接失败,直接触发重连');
            attemptReconnect();
          }
        }
      } else {
        // 如果连接已关闭或不存在,直接触发重连
        if (!isReconnectingRef.current && !isConnecting) {
          console.log('🔗 [WebSocket] 连接已关闭,直接触发重连');
          attemptReconnect();
        }
      }
      return;
    }

    // Ping 成功,直接发送消息
    await sendMessageInternal(data, type, true);
  },
  [pingConnection, sendMessageInternal, attemptReconnect, isConnecting, stopHeartbeat]
);

// 仅发送,不记录历史,适合高频控制输入(带 ping 检测)
const sendMessageNoTrack = useCallback(
  async (data: any, type: 'text' | 'json' = 'text') => {
    // 先进行 ping 检测
    const isConnected = await pingConnection();

    if (!isConnected) {
      // Ping 失败,将消息加入队列并触发重连
      if (config.debug) {
        console.warn('🔗 [WebSocket] Ping 检测失败,高频消息加入队列,触发重连');
      }
      pendingMessagesRef.current.push({ data, type, track: false });

      // 关闭当前连接(如果不是正常关闭),触发重连
      if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
        console.log('🔗 [WebSocket] Ping 检测失败,关闭连接以触发重连');
        try {
          stopHeartbeat();
          wsRef.current.close(1006, 'Ping检测失败');
          setTimeout(() => {
            if (!isReconnectingRef.current && !isConnecting) {
              console.log('🔗 [WebSocket] Ping 检测失败,延迟后直接触发重连');
              attemptReconnect();
            }
          }, 100);
        } catch (error) {
          console.error('🔗 [WebSocket] 关闭连接失败:', error);
          if (!isReconnectingRef.current && !isConnecting) {
            console.log('🔗 [WebSocket] 关闭连接失败,直接触发重连');
            attemptReconnect();
          }
        }
      } else {
        if (!isReconnectingRef.current && !isConnecting) {
          console.log('🔗 [WebSocket] 连接已关闭,直接触发重连');
          attemptReconnect();
        }
      }
      return;
    }

    // Ping 成功,直接发送消息
    await sendMessageInternal(data, type, false);
  },
  [pingConnection, sendMessageInternal, attemptReconnect, isConnecting, config.debug, stopHeartbeat]
);

发送消息机制的优势:

  • 连接验证:发送前验证连接有效性,避免发送到无效连接
  • 消息队列:连接失败时暂存消息,确保消息不丢失
  • 自动重连:Ping 失败时自动触发重连机制
  • 分类处理:区分是否记录历史的消息,灵活处理高频和普通消息

7. URL 规范化处理

在实际开发中,经常遇到 URL 格式不规范的问题。URL 规范化函数能够处理各种格式的输入,确保生成正确的 WebSocket URL。

const normalizeBaseUrl = useCallback((input: string) => {
  let url = (input || '').trim();

  // 🔄 将 HTTP/HTTPS 协议映射为 WebSocket 协议
  // 这是常见的需求,因为很多配置文件中使用的是 HTTP URL
  url = url.replace(/^https:\/\//i, 'wss://');  // 🔒 HTTPS -> WSS
  url = url.replace(/^http:\/\//i, 'ws://');   // 🌐 HTTP -> WS

  // 🔧 修复可能出现的协议嵌套问题
  // 例如:ws://http://host 或 wss://https://host
  url = url.replace(/^wss?:\/\/https:\/\//i, 'wss://');
  url = url.replace(/^wss?:\/\/http:\/\//i, 'ws://');

  // 🎯 如果没有协议前缀,默认补全为 ws://
  if (!/^wss?:\/\//i.test(url)) {
    url = `ws://${url}`;
  }

  // 🧹 去除结尾多余的斜杠,保持 URL 格式统一
  url = url.replace(/\/+$/g, '');

  return url;
}, []);

// 🏗️ 构建完整的 WebSocket URL
const buildWebSocketUrl = useCallback(() => {
  let base = normalizeBaseUrl(config.url);

  // 🤖 如果配置了 robotId,添加到 URL 路径中
  // 这种设计允许服务器根据设备 ID 进行路由和权限控制
  if (config.robotId) {
    // 与后端实际部署保持一致:/api/ws/client/:deviceID
    base += `/api/ws/client/${config.robotId}`;
  }

  // 🛡️ 额外保护:检查是否仍然包含 HTTP 协议
  if (/http:\/\//i.test(base) || /https:\/\//i.test(base)) {
    console.warn('🔗 [WebSocket] 警告:生成的URL包含 http(s) 前缀,疑似协议嵌套:', base);
  }

  return base;
}, [config.url, config.robotId, normalizeBaseUrl]);

URL 规范化的优势:

  • 协议转换:自动将 HTTP 协议转换为 WebSocket 协议
  • 错误修复:修复常见的协议嵌套错误
  • 路径构建:根据配置自动构建完整的连接路径
  • 格式统一:确保生成的 URL 格式统一规范

2. 用户集成 Hook (useWebSocketWithUser)

这个 Hook 将用户信息与 WebSocket 连接集成,提供更高级的功能。它基于基础的 useWebSocket Hook,添加了用户状态管理、自动连接、用户变化时的重连等功能。这种设计使得 WebSocket 连接能够根据用户的登录状态和身份信息自动管理,大大简化了业务代码的复杂度。

接口定义

// 用户集成 WebSocket Hook 的配置选项
export interface UseWebSocketWithUserOptions extends Omit<UseWebSocketOptions, 'config'> {
  baseConfig: Omit<WebSocketConfig, 'robotId'>;  // 基础配置,robotId 会自动从用户信息获取
  autoConnect?: boolean;                        // 是否自动连接(当用户信息加载完成后)
  reconnectOnUserChange?: boolean;              // 当用户信息变化时是否重新连接
}

核心实现

export const useWebSocketWithUser = (options: UseWebSocketWithUserOptions) => {
  const { user, isLoggedIn } = useUser();
  
  // 动态配置管理:根据用户信息动态更新 WebSocket 配置
  const [config, setConfig] = useState<WebSocketConfig>({
    ...baseConfig,
    robotId: user?.robotId,  // 从用户信息中获取 robotId
  });

  // 当用户信息变化时更新配置
  useEffect(() => {
    if (user?.robotId !== config.robotId) {
      const newConfig = {
        ...baseConfig,
        robotId: user?.robotId,
      };
      setConfig(newConfig);

      if (config.debug) {
        console.log('🔗 [WebSocketWithUser] 用户信息变化,更新配置:', {
          oldRobotId: config.robotId,
          newRobotId: user?.robotId,
        });
      }
    }
  }, [user?.robotId, baseConfig, config.robotId, config.debug]);

  // 使用基础 WebSocket Hook
  const webSocket = useWebSocket({
    config,
    ...webSocketOptions,
  });

  // 智能自动连接逻辑
  useEffect(() => {
    const userHasValidId = hasValidRobotId(user);           // 用户是否有有效的 robotId
    const configHasValidId = hasValidRobotId({ robotId: config.robotId }); // 配置是否有有效的 robotId
    const idReady = userHasValidId && configHasValidId && user?.robotId === config.robotId; // ID 是否就绪
    const maxAttempts = config.maxReconnectAttempts ?? 5;

    // 满足自动连接条件时建立连接
    if (
      autoConnect &&
      isLoggedIn &&
      idReady &&
      !webSocket.isConnected &&
      !webSocket.isConnecting &&
      !webSocket.isReconnecting &&
      webSocket.reconnectAttempts < maxAttempts
    ) {
      console.log('🔗 [WebSocketWithUser] 自动连接WebSocket(已绑定 robotId,配置已就绪)');
      webSocket.connect();
    } else if (
      autoConnect &&
      isLoggedIn &&
      idReady &&
      !webSocket.isConnected &&
      webSocket.reconnectAttempts >= maxAttempts
    ) {
      if (config.debug) {
        console.log('🔗 [WebSocketWithUser] 已达到最大重连次数,不再自动连接');
      }
    } else if (autoConnect && isLoggedIn && !userHasValidId) {
      // 未绑定机器人时不进行连接,避免报错与重复重连
      if (config.debug) {
        console.log('🔗 [WebSocketWithUser] 跳过自动连接:未绑定 robotId');
      }
    } else if (autoConnect && isLoggedIn && userHasValidId && !configHasValidId) {
      // 用户已有 robotId,但本地配置尚未同步,等待下一次 effect 周期
      if (config.debug) {
        console.log('🔗 [WebSocketWithUser] 等待配置同步:robotId 尚未写入 config');
      }
    }
  }, [
    autoConnect,
    isLoggedIn,
    user?.robotId,
    config.robotId,
    webSocket.isConnected,
    webSocket.isConnecting,
    webSocket.isReconnecting,
    webSocket.reconnectAttempts,
    webSocket.connect,
    config.debug,
    config.maxReconnectAttempts,
  ]);

  // 登出后主动断开连接
  useEffect(() => {
    if (!isLoggedIn && webSocket.isConnected) {
      console.log('🔗 [WebSocketWithUser] 用户未登录,主动断开WebSocket');
      webSocket.disconnect();
    }
  }, [isLoggedIn, webSocket.isConnected, webSocket.disconnect]);

  // 用户信息变化时重新连接
  useEffect(() => {
    if (reconnectOnUserChange && webSocket.isConnected && user?.robotId !== config.robotId) {
      const hasValidId = hasValidRobotId(user);
      console.log('🔗 [WebSocketWithUser] 用户信息变化,准备重新连接');
      webSocket.disconnect();
    
      // 延迟处理:有有效 robotId 才重新连接;否则保持断开
      setTimeout(() => {
        if (hasValidId) {
          console.log('🔗 [WebSocketWithUser] 重新连接:已绑定有效 robotId');
          webSocket.connect();
        } else if (config.debug) {
          console.log('🔗 [WebSocketWithUser] 跳过重新连接:未绑定 robotId');
        }
      }, 1000);
    }
  }, [
    reconnectOnUserChange,
    webSocket.isConnected,
    user?.robotId,
    config.robotId,
    webSocket.disconnect,
    webSocket.connect,
    config.debug,
  ]);

  // 获取用户相关的调试信息
  const getUserDebugInfo = useCallback(() => {
    return {
      ...webSocket.getDebugInfo(),
      user: {
        isLoggedIn,
        userId: user?.userId,
        userName: user?.name,
        robotId: user?.robotId,
        hasRobotId: hasValidRobotId(user),
      },
    };
  }, [webSocket, isLoggedIn, user]);

  return {
    ...webSocket,
    // 用户相关状态
    user,
    isLoggedIn,
    hasRobotId: hasValidRobotId(user),
    // 用户相关方法
    getUserDebugInfo,
  };
};

用户集成的优势

  • 自动管理:根据用户登录状态自动管理连接
  • 身份识别:自动将用户身份信息添加到连接中
  • 状态同步:用户信息变化时自动重新连接
  • 权限控制:只有登录且有有效身份的用户才能建立连接
  • 调试支持:提供用户相关的调试信息

3. Context 提供者 (WebSocketProvider)

Context 层提供了全局的 WebSocket 状态管理和业务逻辑。这是整个 WebSocket 系统的核心管理层,负责协调各个组件之间的状态共享,处理业务相关的消息逻辑,并提供统一的 API 接口。通过 React Context,可以在应用的任何地方访问 WebSocket 状态和方法,而无需通过 props 层层传递。

新增特性

1. 连接失败弹窗提示

当连接失败时,会自动显示用户友好的弹窗提示,告知用户连接失败的原因和解决方案。

const [showConnectionFailedDialog, setShowConnectionFailedDialog] = useState(false);

// 使用WebSocket hook
const webSocket = useWebSocketWithUser({
  // ... 配置 ...
  onConnectionFailed: () => {
    console.log('🤖 [RobotApp] WebSocket连接失败,显示弹窗提示');
    setShowConnectionFailedDialog(true);
  },
});

// 在组件中渲染弹窗
return (
  <>
    <WebSocketContext.Provider value={contextValue}>{children}</WebSocketContext.Provider>
    
    {/* 连接失败弹窗 */}
    <AppDialog
      visible={showConnectionFailedDialog}
      title="服务连接失败"
      content="无法连接到服务器,请检查网络连接后重试"
      onDismiss={() => setShowConnectionFailedDialog(false)}
      actions={[
        {
          label: '确定',
          mode: 'contained',
          onPress: () => setShowConnectionFailedDialog(false),
        },
      ]}
    />
  </>
);

2. 机器人端特殊处理

在机器人端,禁用了断开连接机制,确保连接始终保持开启状态,确保机器人始终在线。

// 机器人端:禁用关闭机制,确保连接始终开启
const safeDisconnect = useCallback(() => {
  if (Environment.isRobot) {
    console.log('🤖 [RobotApp] 禁用断开连接:机器人端保持连接常开');
    return;
  }
  webSocket.disconnect();
}, [webSocket.disconnect]);

// 在 contextValue 中使用 safeDisconnect
const contextValue: WebSocketContextType = {
  ...webSocket,
  // 替换断开方法(机器人端禁用)
  disconnect: safeDisconnect,
  // ... 其他属性 ...
};

3. 机器人启动状态发送

连接建立后,机器人端会自动发送启动状态消息,告知服务器机器人已上线。

// 机器人启动时发送状态(只发送一次)
useEffect(() => {
  if (webSocket.isConnected && user?.robotId && !startupMessageSent.current) {
    console.log('🤖 [RobotApp] 发送机器人启动状态');
    startupMessageSent.current = true;

    const message = {
      type: 'robot_status',
      robotId: user.robotId,
      status: 'started',
      data: {
        robotId: user.robotId,
        name: user.name || 'Robot',
        version: '1.0.0',
        capabilities: ['health_measurement', 'video_call', 'medicine_management'],
      },
      timestamp: Date.now(),
    };

    // 使用setTimeout来避免在useEffect中直接调用sendMessage
    setTimeout(() => {
      if (webSocket.sendMessage) {
        webSocket.sendMessage(message, 'json');
      }
    }, 100);
  }
}, [webSocket.isConnected, user?.robotId]);

// 当连接断开时重置启动消息标志
useEffect(() => {
  if (!webSocket.isConnected) {
    console.log('🤖 [RobotApp] 连接断开,重置启动消息标志');
    startupMessageSent.current = false;
  }
}, [webSocket.isConnected]);

Context 接口定义

interface WebSocketContextType {
  // 连接状态相关
  isConnected: boolean;          // 是否已连接到服务器
  isConnecting: boolean;          // 是否正在建立连接
  isReconnecting: boolean;        // 是否正在重新连接
  connectionState: 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error';

  // 消息历史相关
  messages: any[];               // 所有消息的历史记录
  messageCount: number;          // 消息总数

  // 周期任务全局事件(供页面订阅)
  periodicEvents: PeriodicEvent[]; // 周期任务事件列表
  periodicEventCount: number;    // 周期事件总数
  clearPeriodicEvents: () => void; // 清空周期事件

  // 连接信息相关
  url: string;                   // WebSocket 连接地址
  lastError: string | null;      // 最后一次错误信息
  reconnectAttempts: number;     // 当前重连次数

  // 基础操作方法
  connect: () => void;           // 建立连接
  disconnect: () => void;        // 断开连接
  sendMessage: (data: any, type?: 'text' | 'json') => void;        // 发送消息(记录历史)
  sendMessageNoTrack: (data: any, type?: 'text' | 'json') => void; // 发送消息(不记录历史)
  clearMessages: () => void;     // 清空消息历史

  // 用户信息相关
  user: any;                     // 当前用户信息
  isLoggedIn: boolean;           // 是否已登录
  hasRobotId: boolean;          // 是否有有效的机器人ID

  // 机器人特定方法
  sendRobotStatus: (status: string, data?: any) => void;        // 发送机器人状态
  sendRobotTask: (taskType: string, taskData: any) => void;     // 发送机器人任务
  sendMeasurementData: (measurementType: string, data: any) => void; // 发送测量数据
  sendHealthData: (healthData: any) => void;                    // 发送健康数据
  sendVideoCallStatus: (status: string, callData?: any) => void; // 发送视频通话状态

  // 消息监听器管理
  addMessageListener: (listener: (message: any) => void) => () => void; // 添加消息监听器

  // 调试方法
  getDebugInfo: () => object;    // 获取调试信息
  getUserDebugInfo: () => object; // 获取用户相关调试信息
}

消息处理机制

Context 层的核心功能之一是处理各种类型的 WebSocket 消息。消息处理机制能够智能识别不同类型的消息,并根据业务需求进行相应的处理。

周期任务事件解析

系统需要特别处理周期任务相关的消息,这些消息通常包含任务状态更新、完成通知等信息。通过智能解析,可以将这些消息转换为用户友好的提示。

const parsePeriodicEvent = useCallback((wsData: any) => {
  // 首先尝试解析消息数据,支持字符串和对象格式
  let raw: any = wsData?.data ?? wsData;
  if (typeof raw === 'string') {
    try {
      raw = JSON.parse(raw);
    } catch {
      // 如果解析失败,将字符串包装为对象
      raw = { content: String(wsData?.data ?? wsData) };
    }
  }

  // 提取实际的数据内容
  const data = raw?.data ?? raw;
  
  // 获取消息的关键字段
  const tag = String(data?.tag || '').toLowerCase();           // 消息标签
  const msgType = String(data?.msgType || raw?.msgType || raw?.type || '').toLowerCase(); // 消息类型
  const action = String(data?.action || raw?.action || '').toLowerCase(); // 操作类型

  // 提取消息内容,支持多种字段名
  const content = data?.content || data?.message || data?.status || raw?.action || JSON.stringify(data);
  const contentStr = typeof content === 'string' ? content : JSON.stringify(content);

  // 智能判断是否为周期任务消息
  let isPeriodic = false;
  const hasPeriodicKeyword = /周期|periodic|cycle/i.test(contentStr || '');
  
  if (msgType === 'notice') {
    // 通知类型消息通常是周期任务
    isPeriodic = true;
  } else if (msgType === 'taskstatus' || msgType === 'task_status') {
    // 任务状态消息,检查标签和内容关键词
    isPeriodic = tag.includes('周期') || hasPeriodicKeyword;
  } else {
    // 其他消息类型,检查标签和内容关键词
    isPeriodic = tag.includes('周期') || hasPeriodicKeyword;
  }

  // 根据操作类型确定消息级别
  const level: 'success' | 'error' = action === 'failed' || action === 'error' ? 'error' : 'success';

  return {
    isPeriodic,
    text: typeof content === 'string' ? content : JSON.stringify(content),
    level,
  };
}, []);

消息监听器管理

为了支持多个组件同时监听 WebSocket 消息,实现了消息监听器模式。这种模式允许任意数量的组件订阅消息,而不会相互干扰。

// 消息监听器列表,使用 Set 避免重复
const messageListeners = useRef<Set<(message: any) => void>>(new Set());

// 添加消息监听器
const addMessageListener = useCallback((listener: (message: any) => void) => {
  messageListeners.current.add(listener);
  console.log('🔗 [WebSocketProvider] 添加消息监听器,当前监听器数量:', messageListeners.current.size);

  // 返回清理函数,组件卸载时自动移除监听器
  return () => {
    messageListeners.current.delete(listener);
    console.log('🔗 [WebSocketProvider] 移除消息监听器,当前监听器数量:', messageListeners.current.size);
  };
}, []);

// 通知所有监听器
const notifyListeners = useCallback((message: any) => {
  messageListeners.current.forEach((listener) => {
    try {
      listener(message);
    } catch (error) {
      console.error('🔗 [WebSocketProvider] 消息监听器执行失败:', error);
    }
  });
}, []);

4. 工具函数 (webSocketGuard)

工具函数层提供了连接条件验证和配置构建功能。这些函数封装了 WebSocket 连接的业务逻辑判断,确保只有在合适的条件下才建立连接,避免无效连接和资源浪费。

核心工具函数

/**
 * 检查用户是否有有效的机器人ID
 * 这是建立WebSocket连接的前提条件之一
 * @param user 用户对象
 * @returns 是否有有效的robotId
 */
export const hasValidRobotId = (user: any): boolean => {
  const rid = user?.robotId;
  // 检查robotId是否为非空字符串
  return typeof rid === 'string' && rid.trim().length > 0;
};

/**
 * 判断是否应该建立WebSocket连接
 * 综合考虑用户登录状态和机器人绑定状态
 * @param user 用户对象
 * @param opts 连接选项
 * @returns 是否应该建立连接
 */
export const shouldConnectWebSocket = (
  user: any,
  opts?: { requireLogin?: boolean; isLoggedIn?: boolean }
): boolean => {
  // 检查登录状态要求
  const loginOk = opts?.requireLogin ? Boolean(opts?.isLoggedIn) : true;
  // 同时满足登录状态和机器人绑定状态
  return loginOk && hasValidRobotId(user);
};

/**
 * 根据用户信息构建WebSocket配置
 * 如果用户未绑定机器人,返回null
 * @param base 基础配置(不包含robotId)
 * @param user 用户对象
 * @returns WebSocket配置或null
 */
export const buildWsConfigWithUser = (
  base: Omit<WebSocketConfig, 'robotId'>,
  user: any
): WebSocketConfig | null => {
  // 如果用户没有有效的robotId,返回null
  if (!hasValidRobotId(user)) return null;
  // 构建完整的配置对象
  return { ...base, robotId: String(user.robotId).trim() };
};

工具函数的优势

  • 业务逻辑封装:将连接条件判断逻辑封装在工具函数中,便于复用和维护
  • 类型安全:提供完整的 TypeScript 类型定义,确保类型安全
  • 灵活配置:支持不同的连接条件组合,适应不同的业务场景
  • 错误预防:在连接前进行条件检查,避免无效连接和错误

在实际项目中, WebSocket 解决方案被广泛应用于各种场景。下面将详细介绍几个典型的使用场景,展示如何在实际开发中应用这些技术。

1. 全局状态管理

在应用根组件中使用 WebSocketProvider 是整个 WebSocket 系统的基础。通过 Context Provider,可以在应用的任何地方访问 WebSocket 状态和方法。

// RobotApp.tsx - 应用根组件
export default function RobotApp() {
  return (
    <Provider store={store}>
      <SafeAreaProvider>
        <PaperProvider theme={theme}>
          {/* WebSocket Provider 包装整个应用 */}
          <WebSocketProvider>
            <NavigationContainer>
              <RootNavigator />
              {/* 全局周期任务提示组件 */}
              <GlobalPeriodicToast />
            </NavigationContainer>
          </WebSocketProvider>
        </PaperProvider>
      </SafeAreaProvider>
    </Provider>
  );
}

关键点说明:

  • Provider 层级:WebSocketProvider 应该放在 Redux Provider 和导航容器内部,确保可以访问用户状态
  • 全局组件:GlobalPeriodicToast 组件可以监听全局的周期任务事件
  • 状态共享:所有子组件都可以通过 useWebSocketContext 访问 WebSocket 状态

2. 连接状态显示组件

创建连接状态显示组件是提供用户反馈的重要手段。用户可以通过这个组件了解当前的连接状态,并在出现问题时采取相应措施。

最新实现特性:

  • 状态可视化:通过颜色、图标、文本直观显示连接状态
  • 详细信息:可选的详细信息显示,包括机器人ID、错误信息、重连次数
  • 交互支持:支持点击事件,可以用于手动重连或显示更多信息
  • 样式灵活:支持自定义样式,适应不同的设计需求
// WebSocketStatus.tsx - 连接状态显示组件
const WebSocketStatus: React.FC<WebSocketStatusProps> = ({ 
  showDetails = false,
  onPress,
  style 
}) => {
  const {
    isConnected,
    isConnecting,
    isReconnecting,
    connectionState,
    lastError,
    reconnectAttempts,
    user,
  } = useWebSocketContext();

  // 根据连接状态获取对应的颜色
  const getStatusColor = () => {
    switch (connectionState) {
      case 'connected': return Colors.success;    // 绿色 - 已连接
      case 'connecting': return Colors.warning;   // 黄色 - 连接中
      case 'reconnecting': return Colors.warning; // 黄色 - 重连中
      case 'error': return Colors.error;          // 红色 - 连接错误
      default: return Colors.textSecondary;       // 灰色 - 未连接
    }
  };

  // 根据连接状态获取对应的文本
  const getStatusText = () => {
    switch (connectionState) {
      case 'connected': return '已连接';
      case 'connecting': return '连接中...';
      case 'reconnecting': return '重连中...';
      case 'error': return '连接错误';
      default: return '未连接';
    }
  };

  // 根据连接状态获取对应的图标
  const getStatusIcon = () => {
    switch (connectionState) {
      case 'connected': return 'check-circle';
      case 'connecting':
      case 'reconnecting': return 'loading';
      case 'error': return 'alert-circle';
      default: return 'circle-outline';
    }
  };

  const StatusContent = () => (
    <View style={[styles.container, style]}>
      <View style={styles.statusRow}>
        <Icon source={getStatusIcon()} size={16} color={getStatusColor()} />
        <Text style={[styles.statusText, { color: getStatusColor() }]}>
          {getStatusText()}
        </Text>
      </View>

      {/* 详细信息显示 */}
      {showDetails && (
        <View style={styles.detailsContainer}>
          {user?.robotId && (
            <Text style={styles.detailText}>机器人ID: {user.robotId}</Text>
          )}
          {lastError && (
            <Text style={[styles.detailText, { color: Colors.error }]}>
              错误: {lastError}
            </Text>
          )}
          {reconnectAttempts > 0 && (
            <Text style={[styles.detailText, { color: Colors.warning }]}>
              重连次数: {reconnectAttempts}
            </Text>
          )}
        </View>
      )}
    </View>
  );

  // 支持点击事件,可以用于手动重连
  if (onPress) {
    return (
      <TouchableOpacity onPress={onPress} activeOpacity={0.7}>
        <StatusContent />
      </TouchableOpacity>
    );
  }

  return <StatusContent />;
};

组件特性:

  • 状态可视化:通过颜色、图标、文本直观显示连接状态
  • 详细信息:可选的详细信息显示,包括机器人ID、错误信息、重连次数
  • 交互支持:支持点击事件,可以用于手动重连或显示更多信息
  • 样式灵活:支持自定义样式,适应不同的设计需求

3. 实时通知处理

在通知页面中处理 WebSocket 消息是实时通信的核心应用场景。通过监听 WebSocket 消息,可以实时更新通知列表,为用户提供及时的信息反馈。

最新实现特性:

  • 智能连接管理:根据用户状态自动管理 WebSocket 连接
  • 消息触发刷新:仅在通知类型(notice)时触发列表刷新,避免高频WS导致接口风暴
  • 节流处理:使用节流技术限制接口调用频率,避免接口风暴
  • 去重机制:使用唯一键进行消息去重,避免重复显示
  • 自动同步:WebSocket 消息和 API 拉取的数据自动合并和同步
// NotifyScreen.tsx - 通知页面组件
const NotifyScreen: React.FC = () => {
  const webSocket = useWebSocketContext();
  const { user } = useUser();
  const [notifications, setNotifications] = useState<NotifyItem[]>([]);

  // 建立WebSocket连接(统一封装:未绑定机器人时跳过连接)
  useEffect(() => {
    const canConnect = shouldConnectWebSocket(user, {
      requireLogin: true,
      isLoggedIn: webSocket.isLoggedIn,
    });

    if (canConnect && !webSocket.isConnected && !webSocket.isConnecting) {
      webSocket.connect();
    } else if (!canConnect && (webSocket.isConnecting || webSocket.isConnected)) {
      // 未满足连接条件时确保不保持连接状态
      webSocket.disconnect();
    }
  }, [user?.robotId, webSocket.isLoggedIn]);

  // 将 WebSocket 消息映射为通知项(适配现有服务端结构)
  const mapMessageToNotifyItem = (msg: any): NotifyItem => {
    // 兼容字符串与对象消息
    let raw: any = msg;
    if (typeof raw === 'string') {
      try {
        raw = JSON.parse(raw);
      } catch {
        raw = { content: String(msg) };
      }
    }

    // 适配服务端返回形如 { data: { content, tag, time, msgType, taskId } }
    const data = raw?.data ?? raw;

    // 内容与时间
    const content = data?.content || data?.message || data?.status || JSON.stringify(data);

    // 统一类型判断
    const tag = String(data?.tag || '').toLowerCase();
    const msgType = String(data?.msgType || raw?.msgType || '').toLowerCase();

    let type: NotifyType = 'all';
    if (tag.includes('周期') || msgType === 'notice') {
      type = 'periodic';
    } else if (tag.includes('健康') || msgType === 'health') {
      type = 'health';
    } else if (tag.includes('电量') || msgType === 'battery') {
      type = 'battery';
    } else {
      type = 'temp';
    }

    return {
      id: `ws_${Date.now()}_${Math.random()}`,
      type,
      title: data?.title || '系统通知',
      content: typeof content === 'string' ? content : JSON.stringify(content),
      time: data?.time || new Date().toISOString(),
      isRead: false,
    };
  };

  // 监听 WebSocket 消息
  useEffect(() => {
    const removeListener = webSocket.addMessageListener((message) => {
      const notifyItem = mapMessageToNotifyItem(message);
      setNotifications(prev => [notifyItem, ...prev]);
    });

    return removeListener;
  }, [webSocket]);

  return (
    <AppLayout>
      <ScrollView>
        {notifications.map(item => (
          <NotifyItemComponent key={item.id} item={item} />
        ))}
      </ScrollView>
    </AppLayout>
  );
};

实现要点:

  • 连接管理:根据用户状态自动管理 WebSocket 连接
  • 消息映射:将 WebSocket 消息转换为通知项格式
  • 类型识别:智能识别不同类型的通知(周期任务、健康消息、电量警告等)
  • 实时更新:通过消息监听器实时更新通知列表
  • 内存管理:组件卸载时自动清理消息监听器

4. 全局周期任务提示

创建全局的周期任务提示组件是提供用户反馈的重要方式。这个组件可以在应用的任何页面显示周期任务相关的通知,确保用户不会错过重要的任务更新。

// GlobalPeriodicToast.tsx - 全局周期任务提示组件
const GlobalPeriodicToast: React.FC<GlobalPeriodicToastProps> = () => {
  const { periodicEvents, periodicEventCount } = useWebSocketContext();
  const lastCountRef = useRef<number>(0);
  const [messages, setMessages] = useState<MessageItem[]>([]);

  // 监听周期任务事件变化
  useEffect(() => {
    if (!periodicEventCount) return;
    if (periodicEventCount <= lastCountRef.current) return;

    // 获取最新的事件
    const lastEvent = periodicEvents[periodicEvents.length - 1];
    lastCountRef.current = periodicEventCount;

    // 在全局(任何页面)显示周期任务轻提示
    const id = `global_periodic_${Date.now()}`;
    const item: MessageItem = {
      id,
      text: String(lastEvent?.text || '周期任务已更新'),
      type: 'success',
      iconType: lastEvent?.operation === 'delete' ? 'delete' : 'success',
      createdAt: Date.now(),
    };
    setMessages((prev) => [...prev, item]);
  }, [periodicEvents, periodicEventCount]);

  // 处理消息移除
  const handleRemove = (id: string) => {
    setMessages((prev) => prev.filter((m) => m.id !== id));
  };

  return <AppMessage messages={messages} onMessageRemove={handleRemove} />;
};

组件特性:

  • 全局显示:可以在应用的任何页面显示周期任务通知
  • 自动管理:自动监听周期任务事件变化,无需手动触发
  • 用户友好:提供清晰的视觉反馈,包括图标和文本
  • 内存优化:自动清理过期的消息,避免内存泄漏

5. 机器人状态发送

发送机器人状态和任务信息是 WebSocket 通信的重要应用场景。通过实时发送机器人的状态更新、任务执行情况、测量数据等信息,可以实现机器人与服务器之间的双向通信。

// 在需要的地方使用 WebSocket 发送机器人状态
const sendRobotStatus = useCallback((status: string, data?: any) => {
  const message = {
    type: 'robot_status',        // 消息类型:机器人状态
    robotId: user?.robotId,      // 机器人ID
    status,                      // 状态信息
    data,                        // 附加数据
    timestamp: Date.now(),       // 时间戳
  };
  webSocket.sendMessage(message, 'json');
}, [webSocket, user?.robotId]);

// 发送机器人任务信息
const sendRobotTask = useCallback((taskType: string, taskData: any) => {
  const message = {
    type: 'robot_task',          // 消息类型:机器人任务
    robotId: user?.robotId,      // 机器人ID
    taskType,                    // 任务类型
    taskData,                    // 任务数据
    timestamp: Date.now(),       // 时间戳
  };
  webSocket.sendMessage(message, 'json');
}, [webSocket, user?.robotId]);

// 发送测量数据
const sendMeasurementData = useCallback((measurementType: string, data: any) => {
  const message = {
    type: 'measurement_data',    // 消息类型:测量数据
    robotId: user?.robotId,      // 机器人ID
    measurementType,             // 测量类型(血压、心率等)
    data,                        // 测量数据
    timestamp: Date.now(),       // 时间戳
  };
  webSocket.sendMessage(message, 'json');
}, [webSocket, user?.robotId]);

// 发送健康数据
const sendHealthData = useCallback((healthData: any) => {
  const message = {
    type: 'health_data',         // 消息类型:健康数据
    robotId: user?.robotId,      // 机器人ID
    data: healthData,            // 健康数据
    timestamp: Date.now(),       // 时间戳
  };
  webSocket.sendMessage(message, 'json');
}, [webSocket, user?.robotId]);

// 发送视频通话状态
const sendVideoCallStatus = useCallback((status: string, callData?: any) => {
  const message = {
    type: 'video_call_status',   // 消息类型:视频通话状态
    robotId: user?.robotId,      // 机器人ID
    status,                      // 通话状态
    callData,                    // 通话数据
    timestamp: Date.now(),       // 时间戳
  };
  webSocket.sendMessage(message, 'json');
}, [webSocket, user?.robotId]);

使用场景:

  • 状态同步:实时同步机器人的运行状态
  • 任务管理:发送任务执行情况和结果
  • 数据采集:实时传输测量和健康数据
  • 通信管理:管理视频通话等通信功能
  • 错误报告:及时报告系统错误和异常

最佳实践总结

在实际开发过程中,积累了许多关于 WebSocket 实现的最佳实践。这些实践不仅提高了代码质量,还增强了系统的稳定性和可维护性。下面将详细介绍各个方面的最佳实践。

1. 连接管理

连接管理是 WebSocket 实现的核心,良好的连接管理能够确保通信的稳定性和可靠性。

✅ 推荐做法:

  • 智能重连机制:使用智能重连机制,避免无限重连

    • 设置最大重连次数限制,防止资源浪费
    • 使用指数退避算法,逐渐增加重连间隔
    • 在重连失败时提供用户友好的错误提示
  • 心跳保活:实现心跳保活,及时检测连接状态

    • 定期发送心跳包,保持连接活跃
    • 根据网络环境调整心跳间隔
    • 心跳失败时自动触发重连
  • 状态管理:根据用户状态自动管理连接生命周期

    • 用户登录时自动建立连接
    • 用户登出时主动断开连接
    • 用户信息变化时重新建立连接
  • 手动控制:提供手动连接/断开控制

    • 允许用户手动重连
    • 提供连接状态显示
    • 支持强制断开连接

❌ 避免的做法:

  • 无限重连:不设置重连次数限制,可能导致资源浪费
  • 忽略状态:忽略连接状态检查,可能导致无效操作
  • 不清理资源:在组件卸载时不清理连接,可能导致内存泄漏
  • 频繁重连:设置过短的重连间隔,可能对服务器造成压力

2. 消息处理

消息处理是 WebSocket 通信的关键环节,良好的消息处理机制能够确保数据的准确传输和正确处理。

✅ 推荐做法:

  • 统一格式:统一消息格式和类型定义

    • 定义标准的消息结构,包含类型、数据、时间戳等字段
    • 使用 TypeScript 接口定义消息类型,确保类型安全
    • 提供消息验证机制,确保数据完整性
  • 监听器模式:实现消息监听器模式

    • 支持多个组件同时监听消息
    • 提供监听器的添加和移除机制
    • 确保监听器的正确清理,避免内存泄漏
  • 历史记录:提供消息历史记录功能

    • 记录发送和接收的消息
    • 支持消息历史查询和清理
    • 提供消息统计信息
  • 消息分类:区分高频和普通消息发送

    • 使用 sendMessage 记录消息历史
    • 使用 sendMessageNoTrack 处理高频消息
    • 根据消息类型选择合适的发送方式

❌ 避免的做法:

  • 直接操作:直接操作 WebSocket 实例,绕过封装层
  • 忽略异常:不处理消息解析异常,可能导致系统崩溃
  • 忽略方向:忽略消息方向标识,可能导致逻辑错误
  • 不验证数据:不验证消息数据格式,可能导致处理错误

3. 错误处理

错误处理是确保系统稳定性的重要环节,良好的错误处理机制能够提高用户体验和系统可靠性。

✅ 推荐做法:

  • 详细错误信息:提供详细的错误信息

    • 记录连接错误的详细信息,包括错误代码和原因
    • 提供消息发送失败的具体原因
    • 记录重连失败的错误信息
  • 优雅恢复:实现优雅的错误恢复机制

    • 在连接失败时自动尝试重连
    • 在消息发送失败时提供重试机制
    • 在解析错误时提供降级处理
  • 错误日志:记录连接和消息错误日志

    • 使用统一的日志格式记录错误信息
    • 提供错误日志的查询和分析功能
    • 支持错误日志的导出和分享
  • 用户提示:提供用户友好的错误提示

    • 将技术错误转换为用户可理解的提示
    • 提供错误解决建议和操作指导
    • 支持错误报告的提交和反馈

❌ 避免的做法:

  • 忽略错误:忽略连接错误,可能导致通信中断
  • 不提供重试:不提供错误重试机制,可能导致功能不可用
  • 错误信息不明确:错误信息不明确,不利于问题定位和解决
  • 不记录日志:不记录错误日志,不利于问题分析和系统优化

4. 性能优化

性能优化是确保 WebSocket 通信高效运行的关键,良好的性能优化能够提高用户体验和系统响应速度。

✅ 推荐做法:

  • 高频消息处理:使用 sendMessageNoTrack 处理高频消息

    • 对于频繁发送的消息,不记录历史,减少内存占用
    • 使用节流和防抖技术,避免消息发送过于频繁
    • 根据消息类型选择合适的发送方式
  • 消息节流:实现消息节流和防抖

    • 使用节流技术限制消息发送频率
    • 使用防抖技术避免重复发送
    • 根据业务需求调整节流和防抖参数
  • 心跳优化:合理设置心跳间隔

    • 根据网络环境调整心跳间隔
    • 避免设置过短的心跳间隔,减少网络开销
    • 在连接空闲时适当延长心跳间隔
  • 资源清理:及时清理不需要的监听器

    • 在组件卸载时清理消息监听器
    • 定期清理过期的消息历史
    • 及时清理不再使用的定时器

❌ 避免的做法:

  • 频繁发送:频繁发送大量消息,可能导致网络拥塞
  • 不清理历史:不清理消息历史,可能导致内存泄漏
  • 心跳过短:设置过短的心跳间隔,可能增加网络开销
  • 不清理监听器:不清理不需要的监听器,可能导致内存泄漏

5. 类型安全

类型安全是确保代码质量和可维护性的重要因素,良好的类型安全能够减少运行时错误和提高开发效率。

✅ 推荐做法:

  • 完整类型定义:使用 TypeScript 提供完整类型定义

    • 为所有接口和数据结构定义明确的类型
    • 使用泛型提供类型推断和复用
    • 提供运行时类型检查,确保数据正确性
  • 消息接口:定义消息接口和枚举

    • 定义标准的消息接口,包含类型、数据、时间戳等字段
    • 使用枚举定义消息类型和状态
    • 提供消息验证机制,确保数据完整性
  • 泛型支持:使用泛型提供类型推断

    • 使用泛型定义可复用的类型
    • 提供类型推断,减少类型声明
    • 支持类型约束,确保类型安全
  • 运行时检查:提供运行时类型检查

    • 在运行时验证消息数据格式
    • 提供类型检查工具和函数
    • 支持类型错误的报告和处理

❌ 避免的做法:

  • 使用 any:使用 any 类型,可能导致类型错误
  • 不定义接口:不定义消息结构,可能导致数据不一致
  • 忽略类型检查:忽略类型检查错误,可能导致运行时错误
  • 不验证数据:不验证消息数据格式,可能导致处理错误

6. 测试策略

测试策略是确保代码质量和系统稳定性的重要手段,良好的测试策略能够提高代码的可信度和可维护性。

✅ 推荐做法:

  • 单元测试:使用 Mock WebSocket 进行单元测试

    • 使用 Mock WebSocket 模拟连接状态和消息
    • 测试连接建立、断开、重连等核心功能
    • 测试消息发送、接收、处理等业务逻辑
  • 状态测试:测试连接状态变化

    • 测试连接状态的正确转换
    • 测试重连逻辑的正确执行
    • 测试错误状态的正确处理
  • 消息测试:测试消息发送和接收

    • 测试消息格式的正确性
    • 测试消息处理的正确性
    • 测试消息监听器的正确性
  • 边界测试:测试错误处理逻辑

    • 测试网络异常的处理
    • 测试消息解析错误的处理
    • 测试重连失败的处理

❌ 避免的做法:

  • 不进行测试:不进行连接测试,可能导致功能不可用
  • 忽略边界:忽略边界情况,可能导致系统不稳定
  • 不测试重连:不测试重连逻辑,可能导致连接不可恢复
  • 不测试错误:不测试错误处理,可能导致系统崩溃

总结

本文通过一个实际的 React Native 项目,详细介绍了 WebSocket 的完整实现方案。从基础原理到架构设计,从核心封装到实际使用,构建了一个功能完善、易于维护的 WebSocket 解决方案。

关键特性

  1. 分层架构:清晰的职责分离,便于维护和扩展

    • 工具层提供基础功能
    • Hook 层封装核心逻辑
    • Context 层管理全局状态
    • 组件层实现业务逻辑
  2. 智能重连:自动重连机制,提高连接稳定性

    • 设置最大重连次数限制
    • 使用指数退避算法
    • 提供用户友好的错误提示
  3. 心跳保活:及时检测连接状态,避免僵尸连接

    • 定期发送心跳包
    • 使用应用层心跳消息格式 { msgType: 'hb' },便于服务端识别
    • 心跳响应超时检测,5秒内未收到响应则判定为连接失败
    • 心跳失败时自动重连
  4. Ping 检测机制:发送消息前验证连接有效性

    • 发送消息前先进行 ping 检测,确保连接真实有效
    • 5秒内未收到响应则判定为连接失败
    • Ping 失败时自动触发重连机制
    • 消息队列暂存,等待连接恢复后自动发送
  5. 消息队列机制:连接失败时暂存消息,确保消息不丢失

    • 连接失败时暂存消息,避免消息丢失
    • 连接恢复后自动发送队列中的消息
    • 区分是否记录历史的消息,灵活处理
    • 达到最大重连次数时自动清空队列
  6. 初始心跳验证:连接建立后验证连接真实性

    • 连接建立后发送初始心跳并等待响应
    • 5秒内未收到响应则判定为连接失败
    • 验证成功后自动启动心跳和消息队列处理
    • 验证失败时自动关闭连接并触发重连
  7. 类型安全:完整的 TypeScript 支持,减少运行时错误

    • 定义完整的类型接口
    • 提供运行时类型检查
    • 支持泛型和类型推断
  8. 用户集成:与用户状态深度集成,提供更好的用户体验

    • 根据用户登录状态自动管理连接
    • 用户信息变化时自动重连
    • 提供用户相关的调试信息
  9. 消息管理:统一的消息处理和监听机制

    • 支持多种消息类型
    • 提供消息历史记录
    • 实现消息监听器模式
  10. 错误处理:完善的错误处理和恢复机制

    • 详细的错误信息记录
    • 优雅的错误恢复
    • 用户友好的错误提示,包括连接失败弹窗
  11. 机器人端特殊处理:机器人端禁用断开连接,确保连接常开

    • 机器人端禁用断开连接机制,确保连接始终保持开启
    • 连接建立后自动发送机器人启动状态
    • 连接断开时自动重置启动消息标志

适用场景

这个 WebSocket 解决方案特别适合以下场景:

  • 实时聊天应用:支持即时消息传输和状态同步
  • 物联网设备监控:实时监控设备状态和数据
  • 游戏实时通信:支持游戏状态同步和玩家交互
  • 协作工具:支持多用户实时协作和编辑
  • 监控和告警系统:实时监控系统状态和告警信息

技术优势

  • 稳定性:通过智能重连和心跳保活确保连接稳定
  • 可维护性:分层架构和清晰的职责分离便于维护
  • 可扩展性:模块化设计支持功能扩展和定制
  • 性能优化:通过消息分类和资源管理提高性能
  • 类型安全:完整的 TypeScript 支持确保代码质量

学习价值

通过本文的学习,读者可以:

  1. 理解 WebSocket 原理:深入理解 WebSocket 协议和通信机制
  2. 掌握架构设计:学习如何设计可维护的 WebSocket 架构
  3. 实践最佳实践:了解 WebSocket 开发的最佳实践和注意事项
  4. 解决实际问题:掌握常见问题的解决方案和优化技巧
  5. 提升开发效率:通过封装和工具函数提高开发效率

未来展望

随着技术的发展,WebSocket 的应用场景将更加广泛。可以考虑以下方向的改进:

  • 协议升级:支持 WebSocket 子协议和扩展
  • 性能优化:进一步优化消息处理和网络性能
  • 功能扩展:支持更多消息类型和业务场景

通过合理使用本文介绍的技术方案,可以构建出稳定、高效的实时通信功能,为用户提供更好的应用体验!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值