Socket.IO协议实现说明.md 9.2 KB

Socket.IO 协议实现说明(完整版)

📋 问题背景

Socket.IO 是一个功能丰富的实时通信库,相比原生 WebSocket 提供了:

  • ✅ 自动重连机制
  • ✅ 心跳检测(ping/pong)
  • ✅ 事件驱动的通信模式
  • 命名空间(namespace)支持 ✅ 已实现
  • 房间(room)支持 ✅ 已实现
  • 二进制数据支持 ✅ 已实现
  • 自动降级连接(WebSocket -> HTTP 长轮询) ✅ 已实现
  • ✅ 握手(handshake)机制

鸿蒙端目前没有现成的 Socket.IO 客户端库,因此需要手动实现 Socket.IO 协议。

🔧 实现方案

1. Socket.IO 协议实现

已实现 SocketIOProtocol 类,支持:

消息类型

  • 0 - CONNECT(连接/握手)
  • 1 - DISCONNECT(断开连接)
  • 2 - EVENT(事件)
  • 3 - ACK(确认)
  • 4 - ERROR(错误)
  • 5 - BINARY_EVENT(二进制事件)✅ 已实现
  • 6 - BINARY_ACK(二进制确认)✅ 已实现

消息格式

  • 事件消息:42["event", data] - 42 是 EVENT 类型,后面是 JSON 数组
  • 命名空间消息:42/namespace["event", data] ✅ 已实现
  • 连接消息:0{"sid":"...","pingInterval":25000,"pingTimeout":20000}
  • Ping 消息:2["ping"]
  • Pong 消息:3["pong"]
  • 二进制消息:51-["event", {"_placeholder":true,"num":0}] ✅ 已实现

握手流程

  1. 建立 WebSocket 连接(或 HTTP 长轮询)
  2. 服务器发送连接消息(包含 SID、pingInterval、pingTimeout)
  3. 客户端保存连接信息,开始心跳机制

心跳机制

  • 客户端定期发送 ping(默认 25 秒)
  • 服务器响应 pong
  • 如果 pong 超时(默认 20 秒),认为连接断开

2. 自动降级连接机制 ✅ 已实现

Socket.IO 的自动降级连接机制:

工作原理

  1. 首选 WebSocket:客户端首先尝试通过 WebSocket 协议与服务器建立连接
  2. 降级到 HTTP 长轮询:如果 WebSocket 不可用(网络限制、防火墙等),客户端会自动降级到 HTTP 长轮询(Long Polling)模式

实现方式

// 1. 先尝试 WebSocket
try {
  transport = new WebSocketTransport();
  await transport.connect(wsUrl, token);
  // WebSocket 连接成功
} catch (e) {
  // WebSocket 失败,降级到 HTTP 长轮询
  transport = new PollingTransport();
  await transport.connect(serverUrl, token, namespace);
  // HTTP 长轮询连接成功
}

HTTP 长轮询实现

  • GET 请求:长轮询接收消息(60 秒超时)
  • POST 请求:发送消息
  • Session 管理:通过 sid 参数保持会话

优势

  • ✅ 提高连接成功率(兼容性更好)
  • ✅ 自动切换,无需手动处理
  • ✅ 透明切换,业务代码无需关心传输方式

3. 命名空间支持 ✅ 已实现

使用方式

// 连接默认命名空间 "/"
await socketService.connect(serverUrl, token);

// 连接自定义命名空间 "/chat"
await socketService.connect(serverUrl, token, "/chat");

// 获取当前命名空间
const namespace = socketService.getNamespace();

实现细节

  • URL 格式:/namespace/socket.io/?EIO=4&transport=websocket
  • 消息格式:42/namespace["event", data]
  • 支持多个命名空间(需要创建多个 SocketIOService 实例)

4. 房间支持 ✅ 已实现

使用方式

// 加入房间
socketService.joinRoom("room_name");

// 离开房间
socketService.leaveRoom("room_name");

实现细节

  • 发送 join 事件到服务器
  • 发送 leave 事件到服务器
  • 服务器负责房间管理(广播消息等)

5. 二进制数据支持 ✅ 已实现

使用方式

// 发送二进制数据
const binaryData = new Uint8Array([1, 2, 3, 4]);
socketService.emitBinary("binary_event", binaryData, (response) => {
  // ACK 回调
  console.log("收到 ACK:", response);
});

实现细节

  • 使用占位符机制:51-["event", {"_placeholder":true,"num":0}]
  • 二进制数据作为单独消息发送
  • WebSocket 直接发送二进制
  • HTTP 长轮询使用 Base64 编码

6. ACK 回调支持 ✅ 已实现

使用方式

// 发送带 ACK 的事件
socketService.emit("event", data, (response) => {
  console.log("收到 ACK:", response);
});

实现细节

  • 消息格式:42["event", data, ackId]
  • ACK 消息:43[data]
  • 使用 Map 存储 ACK 回调

🚀 使用方式

基本使用(与 Android 端一致)

// 1. 获取 Repository
const repository = SocketIORepositoryFactory.getInstance();

// 2. 连接服务器(默认命名空间)
await repository.connect(serverUrl, token);

// 3. 连接命名空间
await repository.connect(serverUrl, token, "/chat");

// 4. 观察连接状态
repository.connectionState.observe((isConnected) => {
  console.log('连接状态:', isConnected);
});

// 5. 发送事件
await repository.sendVehicleControl('lock', vehicleId);

// 6. 发送二进制数据
const binaryData = new Uint8Array([1, 2, 3]);
socketService.emitBinary("binary_event", binaryData);

// 7. 加入房间
socketService.joinRoom("room_1");

// 8. 观察事件
repository.vehicleAlarm.observe((alarm) => {
  if (alarm) {
    console.log('收到报警:', alarm.data);
  }
});

📊 功能对比表

功能 Android 端 鸿蒙端 状态
Socket.IO 协议 ✅ socket.io-client:2.1.2 ✅ 手动实现 ✅ 兼容
握手机制 ✅ 自动 ✅ 手动实现 ✅ 兼容
心跳机制 ✅ 自动 ✅ 手动实现 ✅ 兼容
事件系统 ✅ 支持 ✅ 支持 ✅ 兼容
自动重连 ✅ 支持 ✅ 支持 ✅ 兼容
命名空间 ✅ 支持 已实现 兼容
房间 ✅ 支持 已实现 兼容
二进制数据 ✅ 支持 已实现 兼容
自动降级连接 ✅ 支持 已实现 兼容
ACK 回调 ✅ 支持 已实现 兼容

🔍 自动降级连接详解

为什么需要自动降级?

  1. 网络限制:某些网络环境(企业防火墙、代理服务器)可能阻止 WebSocket 连接
  2. 兼容性:HTTP 长轮询兼容性更好,几乎所有网络环境都支持
  3. 可靠性:如果 WebSocket 失败,自动降级确保连接成功

降级流程

1. 尝试 WebSocket 连接
   ↓
2. WebSocket 连接失败?
   ↓ 是
3. 自动降级到 HTTP 长轮询
   ↓
4. HTTP 长轮询连接成功
   ↓
5. 正常通信(业务代码无感知)

HTTP 长轮询工作原理

客户端                   服务器
  |                        |
  |--- GET (长轮询) ------->|
  |                        | (等待消息,最长 60 秒)
  |<-- 消息1, 消息2 -------|
  |                        |
  |--- GET (长轮询) ------->|
  |                        |
  |--- POST (发送消息) ---->|
  |<-- OK -----------------|
  |                        |

性能对比

特性 WebSocket HTTP 长轮询
延迟 低(实时) 中等(取决于轮询间隔)
带宽 低(无 HTTP 头) 高(每次请求都有 HTTP 头)
兼容性 中等(可能被阻止) 高(几乎都支持)
连接数 1 个长连接 多个短连接

🔍 调试建议

1. 查看协议消息

SocketIOServiceImpl 中已添加详细日志:

  • 连接/断开连接
  • 收到的所有消息
  • Ping/Pong 消息
  • 事件消息
  • 传输方式切换(WebSocket <-> HTTP 长轮询)

2. 验证协议兼容性

如果遇到连接问题,检查:

  1. 服务器是否支持 Socket.IO 2.x 协议(EIO=4)
  2. 握手响应是否包含 sidpingIntervalpingTimeout
  3. 消息格式是否符合 Socket.IO 协议
  4. 是否触发了自动降级(查看日志)

3. 测试自动降级

  • 模拟 WebSocket 失败(断开网络、防火墙阻止)
  • 观察日志中的降级提示
  • 验证 HTTP 长轮询是否正常工作

4. 测试命名空间和房间

  • 连接不同的命名空间
  • 加入/离开房间
  • 验证消息是否正确路由

📝 后续优化

如果需要支持更多 Socket.IO 功能,可以考虑:

  1. 连接状态恢复(Socket.IO 4.6.0+)

    // 恢复连接状态和丢失的数据包
    socketService.reconnect();
    
  2. 多路复用(多个命名空间共享连接)

    // 在同一个连接上使用多个命名空间
    const chatSocket = socketService.of("/chat");
    const notificationSocket = socketService.of("/notification");
    
  3. 压缩支持

    // 启用消息压缩
    socketService.enableCompression();
    

✅ 总结

当前实现已经支持 Socket.IO 的所有核心功能

  • ✅ 完整的协议解析
  • ✅ 握手机制
  • ✅ 心跳机制
  • ✅ 事件系统
  • ✅ 自动重连
  • 命名空间支持
  • 房间支持
  • 二进制数据支持
  • 自动降级连接(WebSocket -> HTTP 长轮询)
  • ACK 回调支持

与 Android 端使用 socket.io-client:2.1.2 的兼容性完全一致,可以正常通信。

自动降级连接机制确保了在各种网络环境下都能成功连接,提高了应用的兼容性和可靠性。