github.com/gorilla/websocket 是生产环境唯一靠谱选择,因其api稳定、文档清晰、错误处理完整,而标准库无原生支持且x/net/websocket已废弃多年。

gorilla/websocket 是唯一靠谱选择
Go 标准库没有原生 WebSocket 支持,golang.org/x/net/websocket 已废弃多年(自 2016 年起不再维护),强行用它会遇到握手失败、协议版本不兼容、无 Ping/Pong 支持等问题。生产环境必须用 github.com/gorilla/websocket —— 它是事实标准,API 稳定、文档清晰、错误处理完整。
常见错误现象:websocket: bad handshake 或 400 Bad Request 响应,基本都是因为用了过时包或没配好 Upgrader.CheckOrigin。
- 开发阶段可设
CheckOrigin: func(r *http.Request) bool { return true },但上线前必须替换为校验实际域名的逻辑 -
Upgrader实例建议全局复用,不要每次请求都新建 - 别漏掉
upgrader.Upgrade()后立即调用defer conn.Close()—— 这只是保底,真正关闭要靠错误检测后显式调用
读写必须分离成两个 goroutine
*websocket.Conn 不是并发安全的:同一连接上并发调用 ReadMessage() 和 WriteMessage() 会 panic 或丢消息。最简可行模式是“一读一写 + channel”:
- 读 goroutine 循环调用
conn.ReadMessage(),把收到的[]byte发到chan []byte - 写 goroutine 从同个 channel 拿数据,调用
conn.WriteMessage() - 两者都需设置 deadline:
conn.SetReadDeadline()和conn.SetWriteDeadline(),否则网络卡住时 goroutine 会永久阻塞 - 任一操作返回
net.ErrClosed或io.EOF,就该退出 goroutine 并关闭连接
容易踩的坑:只开一个 goroutine 边读边写,看似简单,但客户端发得快、服务端处理慢时,WriteMessage() 可能阻塞在缓冲区满,进而拖垮整个连接。
广播场景下别直接遍历 map[*websocket.Conn]bool
需要向所有在线用户推送消息(比如聊天室、通知系统)时,常见错误是维护一个 map[*websocket.Conn]bool,广播时 for-range 遍历并同步 WriteMessage()。这会导致慢连接拖垮快连接 —— 某个手机弱网用户卡住,所有人的消息都延迟。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
正确做法是给每个连接配独立写队列:
- 每个连接启动时创建专属
chan []byte(容量建议 32~64) - 广播逻辑只往各连接的 channel 发消息,不碰
conn.WriteMessage() - 写 goroutine 从自己 channel 消费,失败则清空 channel 并关闭连接
- 用
sync.RWMutex保护连接 map 的增删,读操作(广播遍历)用 RLock,写操作(新连入/断开)用 Lock
性能影响:内存占用略增(每个连接多一个 channel),但吞吐量和响应稳定性显著提升,尤其在连接数 >100 时差异明显。
心跳和优雅关闭不是可选项
WebSocket 连接可能悄无声息断开(NAT 超时、客户端休眠、代理中断),不加心跳,服务端几天后才发现“僵尸连接”。conn.SetPingHandler() 和 conn.SetPongHandler() 必须配对使用:
- 服务端设
conn.SetPingHandler(),每 30 秒自动发 Ping;客户端收到后必须回 Pong - 同时设
conn.SetPongHandler(),更新conn.SetReadDeadline(),确保下次 Ping 前有响应 - 客户端断开时,
ReadMessage()会返回websocket.CloseMessage类型消息,此时应主动调用conn.WriteMessage(websocket.CloseMessage, nil)再conn.Close() - 服务重启前,需遍历连接池发送
CloseMessage,等待所有写 goroutine 退出后再 shutdown HTTP server
最容易被忽略的是:conn.Close() 本身可能阻塞(比如底层 TCP 还在重传),所以不能只靠 defer;必须结合 context 或超时控制,在 goroutine 中调用并 select 等待完成。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










