gorilla/websocket不能用http.handlefunc直接注册,因为其返回后http连接即关闭,而websocket升级需劫持底层tcp连接,upgrade必须在连接释放前完成,否则报err_connection_closed或websocket: bad handshake。

gorilla/websocket 为什么不能用 http.HandleFunc 直接注册
因为 http.HandleFunc 返回后 HTTP 连接就关闭了,而 WebSocket 升级需要劫持底层 TCP 连接——upgrader.Upgrade() 必须在连接未被释放前完成。否则浏览器报 ERR_CONNECTION_CLOSED,服务端抛 websocket: bad handshake。
常见错误写法:
– 把 wsHandler 直接塞进 http.HandleFunc("/ws", wsHandler) 就完事
– 在 handler 外部提前调用 w.WriteHeader() 或写入响应体(哪怕一个空格)
– 忘记检查 err 就直接用 conn,导致 nil pointer panic
正确做法是:用 http.ServeMux 注册路径,或直接传给 http.Server;handler 内必须显式调用 upgrader.Upgrade(w, r, nil),且在此之前不能向 w 写任何内容。
Upgrade 后还能不能继续用 http.ResponseWriter
不能。一旦 upgrader.Upgrade() 成功返回 *websocket.Conn,http.ResponseWriter 就被 hijack(劫持),再调用 w.WriteHeader() 或 w.Write() 会 panic,错误信息是:http: response.WriteHeader on hijacked connection。
典型误用场景:
– Upgrade 后还往 w 写日志、埋点或状态码
– 把 w 传进其他函数,结果里面悄悄写了响应
– 在 defer 中误调 w.Write()(比如想记录连接关闭)
所有后续通信必须走 conn.ReadMessage() 和 conn.WriteMessage(),w 和 r 在 Upgrade 后即失效。
并发读写 *websocket.Conn 为什么会 panic
conn.ReadMessage() 和 conn.WriteMessage() 都不是 goroutine-safe 的。两个 goroutine 同时调用 WriteMessage() 会触发 concurrent write to connection panic;同时读也可能丢消息或阻塞异常。
安全模式只有一条:
– 一个 goroutine 专责 ReadMessage()(通常配合 conn.SetReadDeadline())
– 另一个 goroutine 从 send chan []byte 取消息,串行调用 WriteMessage()
– channel 缓冲区建议设为 16–64,太大易积压内存,太小易丢通知
– 关闭连接前必须 close(c.send),否则 writer goroutine 永远阻塞
别复用同一个 *websocket.Conn 实例做多路写入,也别在 PongHandler 里做耗时操作——它运行在读 goroutine 中,会卡住整个连接的消息流。
CheckOrigin 和心跳设置容易被忽略的细节
Upgrader.CheckOrigin 默认拒绝所有跨域请求,开发时设成 func(r *http.Request) bool { return true } 很方便,但上线不改就是严重安全漏洞——生产环境必须用白名单逻辑校验 r.Header.Get("Origin")。
心跳不是可选配置:
– 客户端静默断连(NAT 超时、WiFi 切换)不会触发 TCP 断开事件
– 服务端必须主动管理活跃状态:用 conn.SetPongHandler 响应 ping,别自己解析 websocket.PingMessage
– 每 20–30 秒客户端发一次 ping,服务端收到 pong 后重置计时器
– 若 60 秒内无读/pong,就主动 conn.Close()
write deadline 超时后,WriteMessage() 返回 net.ErrWriteTimeout,但连接仍处于半开状态——不主动 close,fd 就一直泄漏,Linux 下很快 hit ulimit。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











