WebSocket连接建立后立刻断开,因ResponseWriter被提前写入导致升级失败;WriteMessage需加锁或单goroutine写;未调用PongHandler会导致静默掉线;关闭前须检查conn是否已关闭。

WebSocket 连接建立后立刻断开?检查 http.ResponseWriter 是否被复用
Go 标准库的 net/http 要求每个请求只能写一次响应头;一旦调用过 WriteHeader 或隐式写出状态码(比如往 ResponseWriter 写了内容),再尝试升级协议就会失败,表现为客户端收到 400/500 或直接断连。
- 必须在任何
Write、WriteString、json.NewEncoder().Encode()之前,用upgrader.Upgrade()完成握手 - 别在中间件里提前写日志到
ResponseWriter,它会污染 upgrade 流程 - 如果用了
gorilla/mux,确保路由匹配后没触发其他 handler
典型错误示例:log.Println("handling ws") 放在 Upgrade() 前且该 log 写到了 ResponseWriter —— 实际上很多日志封装会偷偷这么干。
gorilla/websocket 的 WriteMessage 阻塞?并发写需加锁
websocket.Conn 不是并发安全的:多个 goroutine 同时调用 WriteMessage 或 WriteJSON 会 panic,错误信息通常是 concurrent write to websocket connection。
- 每个连接配一个专属写 goroutine,把消息塞进
chan []byte,由它顺序发出 - 读操作可以另起 goroutine,但读到的消息若要广播,务必通过中心化 channel(如
broadcast chan Message)分发,避免直写多个 conn - 别用
conn.SetWriteDeadline()替代锁——它只控制超时,不解决竞态
示例片段:go func() { for msg := range c.send { c.conn.WriteMessage(websocket.TextMessage, msg) } }() 是常见且安全的模式。
立即学习“go语言免费学习笔记(深入)”;
客户端收不到消息?确认 PingHandler 和 SetPongHandler 没覆盖默认行为
浏览器或某些客户端会定期发 Ping 帧,服务端默认每 60 秒自动回 Pong;但一旦你手动设置 upgrader.PingHandler 或 conn.SetPongHandler,就必须显式调用 conn.PongHandler(),否则心跳断裂,连接被静默关闭。
- 最简方案:不设
PingHandler,仅调用conn.SetPongHandler(conn.PongHandler()) - 如果要做自定义逻辑(比如记录 ping 时间),务必在 handler 末尾调用
conn.PongHandler() - 用 Wireshark 或浏览器 DevTools 的 Network → WS → Frames 查看是否有连续的
Ping/Pong交互
漏掉 conn.PongHandler() 是线上环境最隐蔽的掉线原因——没有错误日志,只有“突然失联”。
如何安全关闭连接?别只靠 defer conn.Close()
defer conn.Close() 在 handler 返回时才触发,但 WebSocket 连接可能早已因网络抖动、客户端关页、心跳超时而断开;此时再 close 会 panic:use of closed network connection。
- 所有写操作前加
if conn == nil || conn.IsClosed() { return }判断(IsClosed()是gorilla/websocketv1.5+ 提供的方法) - 读循环中捕获
io.EOF或websocket.CloseMessage后,立即置空 conn 并关闭 send channel - 广播消息前,用
conn.WriteMessage的返回值判断是否已断:if err != nil && !websocket.IsCloseError(err, websocket.CloseAbnormalClosure) { /* 清理 */ }
真实场景里,连接生命周期比代码路径复杂得多;close 不是仪式,而是每次 IO 都要面对的条件分支。










