必须用 sync.rwmutex 或 channel hub 管理 clients,因原生 map 不支持并发读写;broadcast channel 需带缓冲(如 make(chan []byte, 100));http 推送须通过 broadcast

不能在 HTTP handler 里直接遍历 clients 调用 WriteMessage——这会 panic 或卡死,真正的推送必须解耦。
为什么 map[*websocket.Conn]bool 不能裸用?
Go 原生 map 不支持并发读写:一个 goroutine 正在 delete(clients, conn)(断开清理),另一个正在 for conn := range clients(广播),立刻触发 fatal error: concurrent map read and map write。
- 错误现象:服务运行几分钟后突然崩溃,日志只有一行 fatal error,毫无业务上下文
- 正确做法:用
sync.RWMutex包裹所有clients操作;或更推荐——改用注册/注销 channel + 单 goroutine hub 管理,彻底规避锁 - 性能影响:加锁虽安全,但高频注册/注销(如秒级心跳重连)会成为瓶颈;channel 方案天然串行,无竞争,更适合长连接生命周期
broadcast channel 必须带缓冲,且不能是 chan string
用 make(chan []byte, 100) 是底线。无缓冲 chan []byte 或 chan string 都是隐患:
- 前端发消息快、后端写慢(比如某个客户端网络卡顿),
broadcast 会永久阻塞,整个广播链路停摆 -
string类型需转[]byte才能传给WriteMessage,中间隐式分配,GC 压力大;直接传[]byte避免拷贝 - 缓冲大小不是越大越好:设为 100 是经验平衡值——既能抗短时突发,又不会因积压太多导致内存暴涨或消息严重滞后
HTTP 接口如何安全触发 WebSocket 推送?
关键逻辑只有一行:broadcast 。所有“外部事件驱动推送”的场景(如 <code>POST /api/push、订单状态变更回调、定时任务)都走这条路。
立即学习“go语言免费学习笔记(深入)”;
- 常见错误:在
pushHandler里直接for conn := range clients+conn.WriteMessage—— 这既不并发安全,又会让 HTTP 请求阻塞数秒甚至超时 - 必须确保
broadcast有监听 goroutine 在运行,否则该语句会永远卡住(channel 无人接收) - 上线前务必校验 Origin:
CheckOrigin: func(r *http.Request) bool { return r.Header.Get("Origin") == "https://myapp.com" },否则生产环境跨域失败,前端报Connection closed before receiving a handshake response
心跳和连接清理不是可选项,而是存活依据
TCP 连接可能静默断开(NAT 超时、代理 kill 空闲连接),而 Go 代码完全感知不到,直到下次 WriteMessage 才报错——此时已积压多条消息,用户收不到通知。
- 服务端必须主动发 ping:
conn.SetPingHandler(nil)(默认已处理 pong),再用time.Ticker定期conn.WriteMessage(websocket.PingMessage, nil) - 读协程必须设
conn.SetReadDeadline(time.Now().Add(30 * time.Second)),超时即认为离线,主动unregister - 写操作必须检查错误:
if websocket.IsCloseError(err, websocket.CloseAbnormalClosure) || errors.Is(err, io.EOF),满足任一条件就关连接、清clients
真正难的不是连上 WebSocket,而是让成百上千个连接在 24 小时不重启的前提下,不泄漏、不卡死、不丢消息——所有设计都要围绕“连接生命周期可控”展开,而不是“功能跑通就行”。










