多端同步关键在于服务端精准识别并管理各端连接身份,通过 uid+device 区分同一用户的不同设备,再按需定向广播或逐个发送;仅连同一地址或盲目 @SendTo 会导致消息错发、漏发或越权。

多端同步不是靠“连上同一个WebSocket地址”就能自动发生的,关键在于服务端必须识别并管理不同端的连接身份,再按需广播或定向转发。否则手机发的消息只会在本机回显,PC端根本收不到。
WebSocket服务端必须做连接身份区分
原生WebSocket协议本身不带用户或设备标识,wss.on('connection') 每次触发只给你一个裸的 ws 实例。如果你不做任何注册逻辑,所有客户端就是匿名的、不可区分的“路人甲”。
- 必须在首次
ws.on('message')时解析身份信息,比如{ "type": "register", "uid": "u123", "device": "mobile" }或"pc" - 用 Map 或对象缓存连接:
clients.set(uid, { ws, device, lastActive: Date.now() }),不能只存ws - 避免用全局变量
controller/display这种单对单写法——它只支持一台手机控一台电脑,无法扩展到多用户场景 - 微信小程序和 H5 同一账号登录时,
uid相同但device不同,服务端要允许同一uid多个活跃连接
消息路由不能只靠 @SendTo("/topic/xxx")
Spring Boot + STOMP 的 @SendTo 是广播给所有订阅该 topic 的客户端,但“所有”不等于“目标用户的所有端”。如果用户 A 在手机和 PC 都在线,而你只往 /topic/messages 广播,那用户 B 的设备也会收到——这是越权泄露。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 正确做法是服务端接收消息时携带
targetUid,查出该用户所有在线连接,逐个ws.send() - 若用 STOMP,应改用
@SendToUser("/queue/messages"),配合 Spring Security 的 Principal,由框架完成用户级路由 - UNIAPP 等跨端项目慎用 STOMP:微信小程序不支持
sockjs-client,且 STOMP 协议栈体积大,易触发小程序包体积告警 - 自研路由时注意心跳失效清理——某端断开后未及时从
clients中移除,会导致消息发向已关闭的 socket,抛WebSocket is not open
手机端与PC端状态不同步的典型表现和修复点
常见现象是:PC端已读消息,手机端仍显示未读;或手机删了某条聊天记录,PC端还留着。这不是前端没刷新,而是服务端没把“操作指令”当作消息同步出去。
- 所有用户态变更(标记已读、删除消息、修改备注)都必须走 WebSocket 发送一条指令,如
{ "op": "markRead", "msgId": "m456", "uid": "u123" } - 前端不能只依赖本地数据库操作,要等服务端 ACK 后才更新 UI,否则网络抖动会导致状态分裂
- PC端浏览器可能被挂起(标签页休眠),需监听
document.visibilityState,在切回前台时主动拉取一次未同步状态 - 手机端 App 后台时收不到消息,必须配合离线推送(APNs/FCM)唤醒,并在 onResume 时补同步——WebSocket 本身解决不了后台静默问题
真正难的不是连上 WebSocket,而是让每个连接知道自己是谁、能收什么、该回什么。身份管理漏一处,同步就裂一道缝;状态指令少发一条,两端 UI 就开始演双胞胎悖论。

















