WebSocket默认不支持离线消息,因其依赖内存级TCP连接,断连即销毁Session且无内置消息持久化、ID标识与ACK机制;需服务端存消息表和用户已读序列号,并由客户端重连后主动拉取增量消息。

WebSocket 本身不支持离线消息——连接断开,服务端发出去的消息就没了。要实现「客户端重连后自动拉取未读消息」,必须在协议之外补足状态管理、持久化和同步逻辑。
为什么 WebSocket 默认不保存离线消息
WebSocket 是纯内存级长连接,Session 生命周期绑定 TCP 连接。一旦客户端断网、刷新页面或服务端重启,WebSocketSession 就被销毁,所有未确认、未送达的消息直接丢弃。没有内置的 message_id、ack 或存储层,它只管“推”,不管“达”。
常见错误现象包括:
- 用户切后台 2 分钟再回来,错过关键订单通知
- 手机从地铁进电梯瞬间断连,重连后收不到刚才的告警
- 前端没做重连后的消息同步,用户以为“没新消息”,实际漏了 3 条
服务端必须存消息 + 记录最后已读序列号
核心不是“推”,而是“谁在什么位置收到了什么”。推荐用 MySQL(或 Redis)存两条关键数据:
-
message表:字段至少含id、user_id、content、status(0=未推送/1=已送达)、created_at -
user_read_status表:记录每个用户当前已读到的last_seq_id(可用自增主键或时间戳+ID组合)
每次推送前,先查 user_read_status,只推送 id > last_seq_id 的消息;推送成功并收到客户端 ACK 后,更新 last_seq_id。不要依赖内存缓存存序列号——服务重启就丢了。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
客户端重连后必须主动请求增量消息
别等服务端“想起来推给你”。重连成功后第一件事,是向后端发一个 HTTP 请求(比如 /api/v1/messages/since?seq_id=12345),带上自己本地记录的最新 seq_id。服务端返回 id > seq_id 的所有未读消息,并附带这批消息里最大的 id 值。
注意几个实操细节:
- 前端必须持久化存储
last_seq_id(推荐localStorage或 IndexedDB,不用 Cookie) - 首次登录无
seq_id时,传0或null,后端应返回最近 N 条(如 50 条)兜底 - HTTP 拉取和 WebSocket 推送的消息需用同一套
id生成规则(例如数据库自增 ID),否则无法对齐 - 避免在 WebSocket 连接建立成功的回调里立刻发拉取请求——要等握手完成、认证通过、
session绑定好用户身份后再发
消息去重和幂等的关键在客户端
网络抖动可能导致同一条消息被重复推送(服务端重试)或重复拉取(前端重发请求)。解决方式很简单:每条消息带唯一 message_id(服务端生成,如 UUID 或 id),前端用 Set 缓存已处理过的 message_id,渲染前先判断是否已存在。
容易被忽略的一点是:message_id 必须由服务端生成并返回,不能由前端生成。否则多个设备登录同一账号时,ID 冲突或无法排序。
复杂点不在技术选型,而在状态同步的时机和边界——比如用户 A 在设备 1 上读了消息 X,设备 2 还没同步这个“已读”状态,此时设备 2 重连,就会把 X 当作新消息再拉一次。这需要额外的已读广播机制,但那是另一个层级的问题了。

















