WebRTC信令服务器必须严格路由SDP和ICE消息:offer/answer需含fromUserID/toUserID,candidate需含targetUserID;gorilla/websocket是唯一推荐选择,须用sync.Map管理连接、加WriteDeadline、defer关闭,并过滤自身消息以防状态机卡死。

直接用 gorilla/websocket 搭个裸 WebSocket 服务转发 JSON 消息,跑不起来真实 WebRTC 场景——不是连接不上,而是 SDP 乱序、ICE 候选丢失、用户重复进房、连接假死不清理,最终前端报 InvalidStateError 或 Failed to set remote offer sdp: Session error code: ERROR_CONTENT。
roomID 和 clientID 必须原样透传,不能做任何字符串处理
前端发 {"cmd":"register","roomid":"abc","clientid":"u123"},后端解析后必须原样存入映射表,不能 strings.TrimSpace()、不能转小写、不能拼接前缀。否则:
- 客户端反复重连却始终无法加入房间,因为
roomTable.Load(" abc") == nil - 同一用户多个 tab 同时注册,旧连接未被
deregister,导致房间内出现“幽灵客户端” - 忘记启动
roomTable.CleanLoop()(如 AppRTC 中的go rt.cleanLoop()),超时房间堆积,新用户roomid冲突或查不到空闲房间
广播逻辑必须跳过 sender 自身,且按 target 路由 ICE candidate
信令不是群聊消息,WebRTC 状态机对消息来源极其敏感:
-
send消息转发时,必须加if clientID != msg.FromID过滤,否则前端收到自己发的offer,触发setRemoteDescription两次,直接卡死状态机 -
candidate消息体必须含"targetUserID"字段,服务端据此只推给指定接收方;若盲目广播,接收端调用addIceCandidate时会因找不到对应RTCPeerConnection报TypeError: Failed to execute 'addIceCandidate' - SDP
offer/answer必须带"fromUserID"和"toUserID",不能靠房间广播兜底
gorilla/websocket 是唯一推荐选择,别碰 gobwas/ws 或 net/http 升级
看似轻量的 gobwas/ws 在生产环境极易出问题:
- 不自动处理
Ping/Pong,需手动实现心跳;漏掉一次,NAT 超时后连接假死,但服务端仍认为在线 - 遇到
websocket: close 1006 (abnormal closure)时无详细错误上下文,调试成本极高 -
net/http原生升级不校验Sec-WebSocket-Key等协议头,中间代理(如 Nginx、CDN)可能静默断连 -
gorilla/websocket的SetWriteDeadline可控到毫秒级,WriteJSON直接序列化写入,避免json.Marshal+WriteMessage的额外字节拷贝
map[string]*websocket.Conn 不是线程安全的,必须用 sync.Map 或封装结构体
裸 map 并发读写会 panic,但更隐蔽的问题是状态错乱:
- 用户退出时,
delete(roomMap, roomID)和conn.Close()不在同一个 goroutine,导致部分连接残留 - 未加
defer conn.Close()在wsHandler中,goroutine 泄漏,最终触发accept: too many open files - 正确做法:每个
Room实例持有sync.Map(key 为userID,value 为封装了conn和lastSeen的*Participant),天然隔离并发风险
真正难的不是写通 WebSocket,而是让每条信令都落在正确的连接上、按正确的顺序、在正确的生命周期内送达——少一个 if clientID != msg.FromID,前端就可能黑屏;漏一次 defer conn.Close(),服务就可能雪崩。


















