多房间聊天需按room ID分组存储连接并过滤消息,否则导致跨房间泄露;全局map无法识别房间归属,须在握手时提取room ID并持久化;每房间用sync.Map存连接,配独立写协程避免并发写panic。

多房间聊天不是靠“加个参数”就能隔离的,核心是连接必须按 room ID 分组存储、消息必须按 room ID 过滤分发——否则 A 房间的消息必然漏到 B 房间。
为什么全局 map[*websocket.Conn]bool 会导致跨房间消息泄露
常见错误是把所有连接塞进一个 map[*websocket.Conn]bool,广播时遍历全量连接。但 *websocket.Conn 本身不携带 room 信息,服务端也无法从连接对象反查归属。一旦客户端伪造 URL 参数或重放旧请求,就可能混入错误房间。
- gorilla/websocket 的
*websocket.Conn是单次读写绑定的,不支持“按房间广播”语义 - HTTP 升级握手完成后,连接与 URL 查询参数(如
room=lobby)已无绑定关系 - 必须在
@OnOpen或握手阶段提取并持久化 room ID,例如存入conn.UserData(gorilla)或session.getUserProperties().put("room", ...)(Java WebSocket API)
每个房间必须配独立的 sync.Map 和写协程
并发安全的关键不是“锁整个房间”,而是避免对同一 *websocket.Conn 多路写。goroutine 同时调用 conn.WriteMessage() 会 panic:concurrent write to websocket connection。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 为每个房间维护一个
sync.Map[*websocket.Conn]struct{},键是连接,值为空结构体(仅占位) - 每个连接启动专属写协程:
go func() { for msg := range conn.writeCh { conn.WriteMessage(..., msg) } }() - 房间广播时只做
clients.Range(func(k, v interface{}) bool { k.(*websocket.Conn).writeCh ,绝不直接调用 <code>WriteMessage - 连接关闭时务必
close(conn.writeCh)并等待协程退出,否则 channel 阻塞会导致 goroutine 泄漏
room ID 必须服务端校验,禁止前端传参直接用
URL 中的 room=xxx 或消息体里的 {"room":"xxx"} 全部不可信。用户可篡改、重放、暴力枚举。
- 连接建立后第一帧必须是认证消息(如
{"type":"auth","token":"..."}),服务端验证 token 绑定的合法 room/user 对 - 校验通过后,将真实 room ID 写入连接上下文(如 gorilla 的
conn.SetUserData()),后续所有消息路由以此为准 - 收到普通消息时,剥离所有客户端声称的 room/user 字段,只用服务端注入的字段构造广播内容
- 空闲房间需定时清理:用
time.AfterFunc或后台 goroutine 检查clients.Len() == 0后删除该房间实例
Java Spring WebSocket 的 ROOM 映射容易踩的坑
Spring 的 WebSocketSession 不是线程安全的,直接塞进 ConcurrentHashMap<integer map websocketsession>></integer> 仍可能因 session 失效引发 NPE 或重复注册。
- 不要用
session.getId()当 key——它只是连接 ID,和 room 无关;必须用握手时解析出的room做外层 map 的 key - 注册 session 前检查
session.isOpen(),移除前调用session.close()并清空对应Map - 广播时用
session.sendMessage()而非session.getBasicRemote().sendText(),前者自带异常捕获和连接状态检查 - 若用 SockJS 回退,注意其 session ID 和原生 WebSocket 不一致,需统一抽象为逻辑 room ID
真正难的不是写通广播逻辑,而是让每个连接只收自己该收的消息——这要求 room ID 在连接生命周期内唯一、可信、不可篡改,且写操作永远不并发到同一个 conn。任何绕过写协程、跳过服务端校验、复用连接池的做法,上线后都会在高并发下暴露。

















