应使用 gorilla/websocket 而非 net/http 裸升级,因其封装完整帧处理、心跳、错误处理;需正确配置 Upgrader、子协议、读写超时与并发模型,并安全广播及清理连接资源。
用 gorilla/websocket 建连接,别碰标准库 net/http 的裸升级
Go 标准库没有原生 WebSocket 实现,net/http 里所谓“升级”只是帮你处理了握手头,后续帧读写、掩码校验、心跳、关闭流程全得自己写。99% 的场景直接用 gorilla/websocket —— 它是事实标准,稳定、有完整错误处理、支持 ping/pong 自动响应。
常见错误现象:websocket: bad handshake(没走 Upgrader.Upgrade())、连接后立刻断开(没调 conn.SetPingHandler() 或忽略 io.EOF)、并发读写 panic(没加锁或没用 conn.SetReadDeadline())。
-
Upgrader.CheckOrigin默认拒绝非同源请求,开发时设为func(r *http.Request) bool { return true },上线必须收紧 -
Upgrader.Subprotocols要和前端new WebSocket(url, ['json'])的子协议严格匹配,否则握手失败 - 每个连接建议起独立 goroutine 处理读,再用 channel 向写协程投递消息,避免读写竞争
conn.ReadMessage() 和 conn.WriteMessage() 别混用阻塞模式
这两个函数默认是同步阻塞的,但 WebSocket 连接天然双向,读卡住时写也会被拖住。尤其在广播场景下,一个慢客户端会拖垮整个写逻辑。
使用场景:实时日志推送、聊天室消息分发、行情更新 —— 都要求写不被单个连接阻塞。
- 对写操作加超时:
conn.SetWriteDeadline(time.Now().Add(10 * time.Second)),写失败就关连接 - 不要在
ReadMessage()循环里直接调WriteMessage(),改用带缓冲的 channel 中转 - 二进制消息优先用
conn.WriteMessage(websocket.BinaryMessage, data),文本消息注意 UTF-8 合法性,非法字节会触发websocket: bad message
如何安全地广播消息给所有在线连接
没有全局连接列表,靠 map + mutex 管理容易漏锁、panic 或遍历时连接已关闭。更糟的是,直接遍历并调 WriteMessage() 会因某个连接卡住而阻塞整个广播。