gorilla/websocket是首选,因其封装rfc 6455全流程、接口简洁、长期维护且经生产验证;标准库net/http无原生websocket支持,需手动实现握手与帧处理,不现实。

gorilla/websocket 为什么是首选,而不是标准库 net/http
标准库 net/http 没有原生 WebSocket 支持,只提供底层 HTTP 协议能力;想用 WebSocket 就得自己解析握手、处理帧、管理连接状态——几乎没人这么干。而 gorilla/websocket 是事实标准:它封装了 RFC 6455 全流程,暴露简洁接口,且长期维护、被大量生产项目验证。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
go get github.com/gorilla/websocket安装,别碰已归档的golang.org/x/net/websocket - 服务端必须用
http.HandlerFunc处理升级请求,不能直接在http.ServeMux里注册路径后就完事——WebSocket 升级需要手动调用Upgrader.Upgrade() - 注意
Upgrader.CheckOrigin默认拒绝非同源请求,开发时可临时设为func(r *http.Request) bool { return true },但上线前必须收紧
如何安全地管理并发连接和读写竞态
每个 WebSocket 连接对应一个 goroutine,但 *websocket.Conn 的 WriteMessage 和 ReadMessage 不是并发安全的:多个 goroutine 同时写会 panic,同时读可能丢消息或阻塞异常。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 为每个连接单独起两个 goroutine:一个专读(
conn.ReadMessage),一个专写(带缓冲 channel +conn.WriteMessage) - 用
sync.RWMutex保护共享状态(比如用户 ID 到*websocket.Conn的 map),但别锁住整个读写逻辑——只锁增删查操作 - 设置
conn.SetReadDeadline和conn.SetWriteDeadline,否则超时连接会一直占着 goroutine - 关闭连接前务必调用
conn.Close(),否则 fd 泄漏,Linux 下很快 hit ulimit
心跳与连接自动断开怎么写才不掉链子
客户端网络波动、NAT 超时、代理中断都会让连接“假活”:TCP 连接没关,但数据发不出去。单纯依赖 TCP keepalive 不够,必须应用层心跳。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 服务端用
conn.SetPongHandler响应客户端 ping,别自己解析websocket.PingMessage - 客户端每 20–30 秒发一次 ping,服务端收到 pong 后重置该连接的活跃计时器
- 服务端启动定时器(比如
time.AfterFunc),若 60 秒内无任何读/pong,则主动conn.Close() - 别在
PongHandler里做耗时操作——它运行在读 goroutine 中,会阻塞后续消息接收
如何避免 write deadline 超时导致的 panic 和资源残留
write deadline 超时后,WriteMessage 会返回 net.ErrWriteTimeout,但连接对象仍处于半开状态;如果此时不 close,fd 和 goroutine 都不会释放,且下次写依然失败。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 每次
WriteMessage后检查 error,遇到net.ErrWriteTimeout或websocket.ErrCloseSent立即conn.Close() - 不要依赖 defer conn.Close() —— 写失败时 defer 还没触发,连接已卡死
- 在写 goroutine 的 for-select 循环里,把
conn.WriteMessage包进带 timeout 的 context,超时就 break 并 close - 用
conn.SetWriteBuffer(4096)控制缓冲区大小,太小易满,太大延缓感知断连
最常被忽略的是:连接关闭后,要从全局连接池中显式删除对应项。哪怕用了 sync.Map,delete 操作也得在 close 之后立刻执行,否则下一次广播会向已关闭的 conn 写入,触发 panic。










