HTML5WebSocket消息丢包_HTML5重发机制与ack确认实现教程【指南】

6次阅读

HTML5WebSocket消息丢包_HTML5重发机制与ack确认实现教程【指南】

websocket消息丢了,光靠重连没用

重连只能解决连接断开,但断开前发出去的那几条消息,服务器可能根本没收到——前端以为发成功了,后端压根没处理。这时候必须靠应用层确认机制,不是靠网络层或 WebSocket 自带的 ping/pong。

  • onclose 触发时,socket.bufferedAmount 还大于 0,说明有消息卡在浏览器发送队列里,还没真正发出去
  • 服务端主动断连(比如重启)时,不会等客户端消息发完,直接关连接,消息静默丢失
  • 弱网下 send() 调用返回 true,不代表对方收到了,只代表塞进了浏览器的输出缓冲区

必须自己实现 ACK + 消息 ID

WebSocket 协议本身不提供应用级送达保证,你得自己加一层“已读回执”。核心就三样:唯一 ID、ACK 帧、超时重发。

  • 每条业务消息带上客户端生成的 messageId(推荐 crypto.randomUUID(),别用时间戳+随机数拼接,有碰撞风险)
  • 发送后启动定时器,比如 setTimeout(() => resend(msg), 5000),同时把 msg 存进一个待确认 mappendingMap.set(msgId, { msg, timer })
  • 收到服务端返回的 { type: 'ACK', messageId: 'xxx' } 后,clearTimeout(pendingMap.get(msgId).timer) 并删掉记录
  • 重发时要检查是否已送达(比如用户已刷新页面),避免重复提交;服务端也要按 messageId 去重,不能只靠序列号

心跳和 ACK 别混成一件事

心跳(ping/pong)保的是 TCP 连接活着,ACK 保的是某条业务消息被处理了——两者目的不同,不能互相替代,也不能共用同一套超时逻辑。

  • 心跳间隔建议设为 25s,服务端 ping,客户端必须响应 pong;超过 45s 没响应就断连重试
  • ACK 超时建议独立设置,比如 5s,比心跳短,否则你会误以为“连接还活着,消息肯定到了”
  • 别让 ACK 包走心跳通道:心跳帧是控制帧,不能携带业务字段;ACK 必须是普通文本消息,格式统一,例如 {"type":"ack","mid":"abc123"}
  • 服务端收到业务消息后,必须立刻发 ACK,不能等 DB 写完再发——否则 ACK 延迟导致前端反复重发

离线消息兜底不能只靠重发

用户切后台、关页面、杀进程,重发机制完全失效。ACK 只能解决“在线时的瞬时丢包”,离线状态得靠服务端缓存 + 状态同步。

立即学习前端免费学习笔记(深入)”;

  • 服务端收到消息但目标用户不在线,不能丢弃,要写入 redis数据库,key 用 userId:messageId,带过期时间(比如 7 天)
  • 用户重连成功后,客户端要主动发一个 { type: 'fetch_offline', lastSeq: 12345 },服务端返回未读消息列表
  • 注意:离线消息也要走 ACK 流程,否则“拉取成功”不等于“用户看到”,下次拉又会重复推
  • 如果消息量大,别一次性全推,按分页或时间窗口(如最近 1 小时)控制,避免首屏卡顿

最容易被忽略的是:ACK 不是发出去就完了,它本身也是一条网络消息,也可能丢。所以服务端发 ACK 之前,得先落库标记“已触发 ACK”,失败则重试;客户端收 ACK 后,也要发个“ACK-RECEIVED”给服务端——这层嵌套确认,才是生产环境扛住弱网的关键。

text=ZqhQzanResources