不能在HTTP Handler中直接调用c.WriteMessage(),因*websocket.Conn非并发安全,多goroutine写会panic或阻塞,且长连接与短生命周期Handler状态不同步;须用广播channel解耦,由独立goroutine串行发送。

直接用 websocket.New 在 HTTP Handler 里发消息会 panic 或卡死,必须用广播 channel 解耦触发与发送逻辑——这是上线前最容易被忽略、但最致命的一环。
为什么不能在 HTTP 接口里直接调用 c.WriteMessage()
常见错误是:收到 POST /api/push 请求后,遍历所有已连接的 *websocket.Conn 并挨个 WriteMessage()。结果不是 panic 就是 goroutine 堵死。原因很实在:
-
*websocket.Conn不是并发安全的,多个 goroutine 同时写会冲突或静默断连 - 某个客户端网络卡住或已断开,
WriteMessage()会阻塞当前 goroutine,拖垮整个广播流程 - HTTP handler 生命周期短,而 WebSocket 连接是长生命周期,状态无法可靠同步
如何用 websocket.New + 广播 channel 构建安全推送链路
核心是把“谁要发”和“怎么发”彻底分开。所有推送请求(比如运营后台发公告)先写进一个带缓冲的 channel,再由单独的 hub.run() goroutine 拉取、过滤、分发。
- 定义广播通道:
broadcast = make(chan []byte, 100)(缓冲大小按峰值 QPS 估算) - 每个连接在
websocket.New回调中启动读/写 goroutine,并注册进全局clientsmap(用sync.Map) - 另起一个
go hub.run(),持续从broadcast读数据,遍历clients调用conn.WriteMessage() - HTTP 接口只需做:
hub.broadcast ,完全不碰连接对象
CheckOrigin 和跨域握手失败的硬核排查点
前端连不上 ws://localhost:3000/ws?99% 是 CheckOrigin 拦截导致握手失败,不是协议问题。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 开发阶段可临时放行:
CheckOrigin: func(r *http.Request) bool { return true } - 上线必须白名单:
return r.Header.Get("Origin") == "https://myapp.com" - 若前端走 Nginx,确认加了:
proxy_set_header Origin $http_origin; - 前端 URL 必须是
ws://或wss://,写成http://浏览器根本不会发起升级请求
用 Fiber 实现 SSE 推送的轻量替代方案
如果业务只需要服务器单向通知(如系统公告、日志流),SSE 比 WebSocket 更省心,且天然支持自动重连和事件类型标记。
- Fiber 中直接用
c.Set("Content-Type", "text/event-stream")和c.Set("Cache-Control", "no-cache") - 每次推送写入格式为:
"event: notice\ndata: {\"msg\":\"hello\"}\n\n" - 注意:不要用
c.SendString(),要用c.Context().Response.BodyWriter().Write()避免缓冲区截断 - SSE 连接数高时,记得限制每个用户的最大连接数,避免 fd 耗尽
真正难的不是连上 WebSocket,而是让成百上千个连接在高并发下稳定收发、不丢不卡、能灰度、可监控——这些都得靠 channel 的缓冲策略、连接生命周期管理、以及对 WriteMessage() 阻塞行为的预判来兜底。

















