"websocket: the client is not using websocket"错误源于http请求缺少upgrade头,常见于前端误用fetch而非new websocket、反向代理未透传upgrade/connection头、中间件提前读body或checkorigin拒绝非同源请求。

为什么 gorilla/websocket 的 Upgrader.Upgrade() 会返回 "websocket: the client is not using WebSocket"
这个错误不是客户端“没连上”,而是 HTTP 请求根本没带 WebSocket 升级头。常见于前端用 fetch() 或 XMLHttpRequest 发请求,但没走 new WebSocket();或者 Nginx/反向代理没透传 Connection 和 Upgrade 头。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 检查前端是否真的用了
new WebSocket("ws://..."),而不是fetch()模拟 - 后端路由必须是普通 HTTP handler,不能套在中间件里提前读取
Request.Body(会消耗 body,导致 upgrade 失败) - Nginx 配置里必须显式透传:
proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade"; -
Upgrader.CheckOrigin默认拒绝非同源请求,开发时可临时设为func(r *http.Request) bool { return true },但上线前务必改回严格校验
conn.WriteMessage() 阻塞、超时、连接突然断开怎么办
WebSocket 是全双工但非线程安全的:同一个 *websocket.Conn 不能并发调用 WriteMessage() 或 ReadMessage(),否则 panic;同时它默认不设写超时,一旦对端卡住或网络中断,WriteMessage() 可能永久阻塞。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 每个连接起两个 goroutine:一个专管读(
ReadMessage()),一个专管写(WriteMessage()),用 channel 中转消息,避免竞态 - 给连接设写超时:
conn.SetWriteDeadline(time.Now().Add(10 * time.Second)),每次写前都重设 - 捕获
write: broken pipe、use of closed network connection这类错误,立刻conn.Close()并清理资源 - 不要在
WriteMessage()后立刻defer conn.Close()—— 写失败时连接可能还开着,得靠读 goroutine 收到io.EOF或websocket.CloseMessage才关
如何安全地广播消息而不阻塞其他连接
直接遍历所有 *websocket.Conn 并调用 WriteMessage() 是危险的:某个连接慢或已断,就会拖慢整个广播,甚至导致其他连接超时断开。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 每个连接维护独立的写 channel(如
chan []byte),写 goroutine 从 channel 取消息发送,channel 设小缓冲(如 32),满时直接丢弃旧消息(用select { case c.send ) - 广播时只把消息发到每个连接的 channel,不等写完成;写 goroutine 自己处理重试和错误退出
- 连接管理用
map[*websocket.Conn]struct{}+sync.RWMutex,读多写少场景下比sync.Map更直观可控 - 避免在广播循环里做任何阻塞操作(比如查 DB、调外部 API),消息内容应预先序列化好
为什么 gorilla/websocket 不支持 HTTP/2 的 WebSocket 升级
Go 标准库的 http.Server 在启用 HTTP/2 后,会绕过 http.Handler 的常规流程,直接复用 TLS 连接帧,而 gorilla/websocket 依赖 HTTP handler 的原始 net.Conn 做协议切换,两者底层机制冲突。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 线上服务若需 HTTP/2,WebSocket 路径(如
/ws)应单独用 HTTP/1.1 server 暴露,或通过反向代理(Nginx、Traefik)将 WebSocket 流量剥离并转发给独立的 HTTP/1.1 后端 - 不要尝试用
http2.ConfigureServer()强行兼容 ——Upgrader.Upgrade()会静默失败或 panic - 本地开发用
http.ListenAndServeTLS()启 HTTP/2 是没问题的,但必须确保浏览器访问的是 HTTPS 地址(HTTP/2 不支持明文),且 WebSocket URL 是wss://
真正麻烦的从来不是握手或发消息,而是连接生命周期管理——谁负责关、什么时候关、关了之后状态怎么清理。这些细节不写进逻辑,跑几天就出问题。










