应使用 gorilla/websocket 实现 WebSocket 长连接推送,避免轮询;需用 upgrader.Upgrade() 升级连接,读写分离 goroutine,写操作加锁,用 sync.Map + 心跳检测管理连接,推荐 atomic 生成 ID,按场景选 WriteMessage 或 WriteJSON,Nginx 反代须配置三项关键参数。

用 net/http + gorilla/websocket 做基础长连接推送,别碰轮询
HTTP 轮询(包括短轮询、长轮询)在实时性、连接开销和服务器负载上都远不如 WebSocket。Go 原生 net/http 不直接支持 WebSocket 协议,必须借助标准库外的成熟实现——gorilla/websocket 是当前最稳定、文档最清晰、被大量生产项目验证的选择。
常见错误是试图用 http.HandlerFunc 直接读写 ResponseWriter 模拟“流式推送”,结果触发 HTTP 连接关闭或响应头已发送错误(http: multiple response.WriteHeader calls)。
- 初始化连接时,务必用
upgrader.Upgrade()将 HTTP 连接升级为 WebSocket,之后所有通信走*websocket.Conn - 每个连接需单独 goroutine 处理读/写,避免阻塞;写操作必须加锁(
conn.WriteMutex.Lock()),因为WriteMessage非并发安全 - 客户端断连后,
ReadMessage会返回io.EOF或*websocket.CloseError,此时应主动conn.Close()并清理对应连接状态
map[uint64]*websocket.Conn 存连接?小心并发 panic 和内存泄漏
用简单 map 存连接看似直观,但 Go 的原生 map 并发读写会直接 panic(fatal error: concurrent map read and map write)。更隐蔽的问题是:连接断开后若没及时从 map 删除,goroutine 持有 *websocket.Conn 引用,GC 无法回收,连接对象+缓冲区持续占内存。
- 改用
sync.Map只解决并发安全,不解决泄漏;必须配合心跳检测(如每 30sconn.WriteMessage(websocket.PingMessage, nil))+SetPingHandler回调来识别失活连接 - 连接 ID 别硬编码用
int或time.Now().UnixNano(),推荐用atomic.AddUint64(&idSeq, 1)生成唯一 uint64 ID,避免重复和溢出 - 实际业务中,连接常需绑定用户身份(如 user_id),建议用二级结构:
map[int64][]*clientConn,其中*clientConn封装*websocket.Conn+ 心跳时间戳 + 关闭 channel
推送消息用 conn.WriteJSON() 还是 conn.WriteMessage()?看场景
WriteJSON() 内部调用 json.Marshal() 再传给 WriteMessage(),多一次序列化开销。对高频小消息(如“用户 A 已上线”),直接构造 []byte 或复用 bytes.Buffer 更高效;但若消息结构复杂、字段动态,WriteJSON() 省心且不易错。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 避免在推送前对整个消息体做
json.Marshal()后再传给WriteMessage(websocket.TextMessage, data)—— 这等于手动重复WriteJSON()的工作 - 二进制协议(如 Protobuf)推送时,必须用
WriteMessage(websocket.BinaryMessage, data),且客户端需明确按 Binary 类型接收,否则浏览器会报Invalid frame header - 单条消息超过 64KB 默认限制会触发
websocket: write deadline exceeded,需提前调用conn.SetWriteDeadline(),或拆分消息(服务端分片 + 客户端拼接)
部署时 nginx 反代 WebSocket 必须配这三行,否则 400 或连接秒断
线上几乎必过 nginx,但默认配置会破坏 WebSocket 升级流程,导致客户端收到 400 错误或连接建立后立即关闭。关键不是“支持 WebSocket”,而是透传升级请求头和保持长连接。
-
proxy_http_version 1.1;—— HTTP/1.0 不支持 Upgrade 头 -
proxy_set_header Upgrade $http_upgrade;—— 把客户端的Upgrade: websocket头透传给后端 -
proxy_set_header Connection "upgrade";—— 告诉 nginx 此连接要升级,不要按普通 HTTP 处理
漏掉任意一行,upgrader.Upgrade() 都会返回 websocket: the client is not using the websocket protocol。测试时可用 wscat -c ws://your-domain.com/ws 快速验证是否通。
真实环境里,连接数上万后,单机 gorilla/websocket 性能足够,瓶颈往往在 TCP 参数(如 net.core.somaxconn)、文件描述符限制(ulimit -n)和心跳超时设置,而不是框架本身。

















