不设CheckOrigin会403,不分离读写goroutine会panic,不加心跳和写超时会积压“假在线”连接;三者均为gorilla/websocket上线必调项。

直接用 gorilla/websocket 搭,但不设 CheckOrigin 会 403,不分离读写 goroutine 会 panic,不加心跳和写超时会积压“假在线”连接——这三处不处理,项目上线不到一天就会出问题。
为什么 Upgrade 后立刻设 CheckOrigin
浏览器发起 ws:// 连接时,默认带 Origin 头;gorilla/websocket 的 Upgrader 默认拒绝所有非同源请求,不设 CheckOrigin 就直接返回 403。开发阶段可临时写成 func(r *http.Request) bool { return true },但必须清楚:这不是“绕过”,而是显式覆盖安全默认值。
- 生产环境必须校验
r.Header.Get("Origin")是否在白名单内,或结合X-Auth-Token验证身份 - 如果前端指定了子协议(如
new WebSocket(url, ["json"])),服务端upgrader.Subprotocols必须显式匹配,否则连接秒断且无明确错误 -
Upgrade()调用后,http.ResponseWriter立即失效,不能再调用w.Write()或设置 header,否则 panic
读写 goroutine 为什么必须分离
*websocket.Conn 的 ReadMessage 和 WriteMessage 不是并发安全的。多个 goroutine 同时调用 WriteMessage,轻则消息错乱(A 用户消息被 B 收到),重则触发 write tcp: use of closed network connection。
- 正确做法:每个连接启动两个 goroutine ——
readPump专责读、转发消息到中心 hub;writePump从专属 channel 消费待发消息,顺序调用conn.WriteMessage - 别用
sync.Mutex包裹写操作,锁住整个写流程会导致广播延迟飙升,尤其在高并发场景下 -
writePump启动前必须监听conn.CloseChan(),一旦收到关闭信号就退出,避免往已关连接发数据
怎么识别并清理“假在线”连接
所谓“假在线”,是指 TCP 连接还挂着,但客户端早已失联(切后台、锁屏、NAT 超时)。这类连接不主动清理,广播时会卡死 goroutine,最终耗尽资源。
立即学习“go语言免费学习笔记(深入)”;
- 服务端必须主动发
websocket.PingMessage(建议每 30 秒一次),不能只等客户端 ping - 用
conn.SetPongHandler响应 pong,并在 handler 里更新该连接的最后活跃时间 - 每次
WriteMessage前设conn.SetWriteDeadline(time.Now().Add(10 * time.Second)),超时就close并从 clients map 中移除 - 广播循环中对每个连接做
if err != nil || conn.IsClosed() { delete(clients, conn); conn.Close() }
Hub 广播时最容易漏掉的细节
广播不是简单遍历 map 发一遍就完事。真正稳定运行的关键,在于每个连接的 readPump 和 writePump 必须成对启停:少关一个,就是 goroutine 泄漏;多关一个,可能误杀还在收消息的连接。
- 别依赖
defer conn.Close(),它只保证连接结束时关闭,不保证两个 pump 协程同步退出 - 推荐用一个
donechannel 显式通知两个 pump 退出,再用sync.WaitGroup等待它们结束 - 广播前检查
conn.WriteDeadline是否已过期,比单纯查IsClosed()更早发现异常连接


















