Go WebSocket 服务需谨慎使用 gorilla/websocket:默认不处理连接生命周期、并发写不安全、无超时机制;须手动调用 net/http Upgrade 完成握手,再由 gorilla 封装连接;读写需分离 goroutine,避免竞态。

Go 做 WebSocket 服务,别直接上 gorilla/websocket 就开干——它默认不处理连接生命周期、不防并发写、不设超时,线上跑半天就 panic 或丢消息。
为什么 net/http 自带的 Upgrade 要手动调用
Go 标准库没封装 WebSocket 协议逻辑,http.ResponseWriter 和 *http.Request 只能帮你完成 HTTP 握手(即 101 Switching Protocols),真正的帧读写得自己接管底层连接。
-
gorilla/websocket的Upgrader.Upgrade()就是干这事:检查Upgrade: websocket头、校验Sec-WebSocket-Key、写响应头,然后把http.Hijacker拿到的裸net.Conn包装成*websocket.Conn - 漏掉
defer conn.Close()或在Upgrade后还往http.ResponseWriter写数据,会触发http: response.WriteHeader on hijacked connection - 如果用了反向代理(如 Nginx),必须显式开启
proxy_set_header Upgrade $http_upgrade;,否则Upgrade头被吃掉,握手直接失败
gorilla/websocket 的读写并发安全怎么破
*websocket.Conn 的读方法(ReadMessage)和写方法(WriteMessage)**各自线程安全,但读写之间不互斥**。一个 goroutine 在 WriteMessage,另一个同时 ReadMessage,底层 TCP 连接可能乱序或阻塞。
- 标准做法:每个连接起两个 goroutine —— 一个专读(
for { conn.ReadMessage(...) }),收到消息后发到 channel;另一个专写(for { msg := ) - 千万别在读 goroutine 里直接
conn.WriteMessage(),尤其当写逻辑涉及 DB 查询或 HTTP 调用,延迟一上来,读缓冲区就满,下一次ReadMessage直接 timeout -
SetWriteDeadline()必须设,否则网络抖动时WriteMessage卡住,整个写 goroutine 就挂死;建议设为time.Now().Add(10 * time.Second)
如何安全地广播消息而不 panic
广播本质是遍历所有活跃连接并调用 WriteMessage,但连接可能随时断开,conn.WriteMessage() 返回 websocket.ErrCloseSent 或 io.EOF 是常态。
立即学习“go语言免费学习笔记(深入)”;
- 每次写前先检查
conn.IsClosed()(gorilla/websocketv1.5.0+ 支持),或者更稳妥:捕获错误,遇到websocket.ErrCloseSent、io.ErrUnexpectedEOF、net.ErrClosed就从连接池中移除该conn - 不要用 map 存连接:
map[string]*websocket.Conn并发读写要加锁;改用sync.Map或更推荐——把连接注册进一个带读写锁的结构体,写广播时只读,清理时写锁 - 广播量大时(比如千级连接),避免在主线程循环写,改用 worker pool:把待发消息塞进任务队列,固定几个 goroutine 消费,防止瞬时高负载打爆内存
心跳和连接超时为什么不能只靠 SetPingHandler
SetPingHandler 只负责“收到 ping 自动回 pong”,它不解决客户端假死(比如手机切后台、WiFi 断开但 TCP 连接未 RST)的问题。
- 必须配对使用
SetPongHandler+ 定期conn.SetReadDeadline():每次收到消息或 pong 后重置 deadline,比如conn.SetReadDeadline(time.Now().Add(30 * time.Second)),超时后ReadMessage返回 error,此时关闭连接 - 客户端不发 pong?那你的
SetPongHandler根本不会被调用,得靠服务端主动 ping:conn.SetWriteDeadline()后调conn.WriteMessage(websocket.PingMessage, nil),再等 pong —— 但这需要你自己维护 ping/pong 状态机,gorilla/websocket不提供内置支持 - 别信 “设了
WriteDeadline就能自动断连”:它只让单次WriteMessage失败,你得在错误分支里显式conn.Close(),否则连接一直挂着
真正难的不是写通 WebSocket,而是让连接在弱网、重启、扩容、长连接堆积下不泄漏、不 panic、不丢帧——这些细节藏在 SetReadDeadline 的时机、conn.Close() 的位置、以及每次 WriteMessage 后是否检查错误里。


















