gorilla/websocket是当前最稳、文档最全、社区支持最好的选择;golang.org/x/net/websocket已弃用,不支持子协议协商、ping/pong心跳、超时管理,且在go 1.16+中存在兼容性问题,易致连接中断、goroutine泄漏和panic。

直接说结论:Golang 实现即时通讯,gorilla/websocket 是当前最稳、文档最全、社区支持最好的选择;golang.org/x/net/websocket 已被官方标记为 deprecated,不建议新项目使用。
为什么不用 golang.org/x/net/websocket?
这个包早在 2015 年就停止维护,Go 官方明确标注为「unmaintained」。它不支持 WebSocket 协议的完整子协议协商、缺少对 ping/pong 心跳帧的显式控制、没有内置超时管理,且在 Go 1.16+ 中已出现兼容性问题(比如 websocket.Conn 的 Write 方法可能 panic)。实际部署中,你大概率会遇到连接突然中断却无错误回调、客户端反复重连但服务端残留 goroutine 等问题。
常见错误现象包括:
-
panic: send on closed channel—— 因底层连接关闭后仍尝试写入 - 客户端显示已连接,但
ReadMessage()阻塞不返回,也无 error - 服务端内存持续上涨,
pprof显示大量 goroutine 停在io.ReadFull
gorilla/websocket 连接建立与升级的关键点
核心是 Upgrader 实例,它负责把标准 HTTP 请求“升级”为 WebSocket 连接。这一步最容易出错的是跨域和握手头校验。
必须注意的配置项:
-
CheckOrigin:生产环境不能设为func(r *http.Request) bool { return true },应校验r.Header.Get("Origin")是否在白名单内 -
HandshakeTimeout:建议设为10 * time.Second,避免慢客户端拖垮握手流程 -
ReadBufferSize和WriteBufferSize:默认 4096 字节,若消息普遍大于 8KB,需手动调大,否则会触发额外内存拷贝 - 务必设置
upgrader.Error回调,捕获并记录握手失败原因(如非法Sec-WebSocket-Key)
示例片段:
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return strings.HasPrefix(r.Header.Get("Origin"), "https://myapp.com")
},
HandshakeTimeout: 10 * time.Second,
ReadBufferSize: 8192,
WriteBufferSize: 8192,
}
消息收发循环里必须处理的三件事
每个连接的读写逻辑通常写在一个 goroutine 里,但仅用 ReadMessage() / WriteMessage() 是不够的。
必须显式处理:
-
连接存活判断:靠
SetPingHandler+SetPongHandler,否则 NAT 或代理可能静默断连。服务端应调用conn.SetPingHandler(nil)让库自动回 pong,并定期发 ping(例如每 30 秒) -
写操作并发安全:WebSocket 连接本身不是线程安全的,多个 goroutine 同时调用
WriteMessage()会 panic。正确做法是用一个专属的 write goroutine + channel 接收待发送消息 -
读错误退出后必须关闭连接:
ReadMessage()返回 error 后,应立即调用conn.Close(),否则连接资源不会释放,netstat -an | grep :8080 | wc -l会持续上涨
JWT 认证不能只做一次握手校验
很多人在 Upgrade 阶段解析 URL 参数或 header 中的 token,然后放行连接——这是危险的。因为 WebSocket 握手完成后,后续所有帧都不再携带 HTTP 头,也无法再验证身份。
更稳妥的做法:
- 握手阶段只做最低限度校验(如签名有效、未过期),拿到用户 ID 后存入
conn的自定义字段(例如用context.WithValue包装 request) - 首次
ReadMessage()收到的数据必须是登录包(如{"cmd":"auth","token":"..."}),再次校验 token 并绑定用户 session - 之后所有消息都通过该 session 查找目标连接,避免“连接已建立但身份未确认”的中间态
忽略这点,攻击者可伪造任意 client ID 直接接入,绕过全部权限控制。
真正难的不是连上,而是让成千上万个连接长期稳定在线、不泄漏、不堆积、不误发。心跳机制、连接池清理、消息广播的扇出方式——这些细节才决定你的 IM 服务能不能扛住真实流量。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











