正确做法是分离连接管理与消息分发:维护客户端连接池,通过 channel 或 map 管理活跃连接,另启 goroutine 监听广播通道,由 HTTP 接口、定时任务等外部事件触发向指定或全体客户端推送。

用 gorilla/websocket 实现基础消息推送,核心不是“连上就发”,而是“连上后能被外部触发推送”——比如 HTTP 接口调用、定时任务或业务事件触发。直接在 WebSocket handler 里写死 WriteMessage 只能回显,无法做到“服务端主动推”。
为什么不能只靠 handleConnections 循环读写?
单纯在连接处理函数里 for { conn.ReadMessage(); conn.WriteMessage(...) } 只能实现回显或点对点 echo,无法响应外部事件(如管理员发公告、订单状态更新)。真正的推送必须解耦:连接管理 + 消息分发分离。
- 常见错误现象:
http.HandleFunc("/ws", ...)里没启动广播 goroutine,导致broadcastchannel 无人监听,broadcast 永远阻塞 - 关键设计点:必须用一个全局
chan(如broadcast)作为消息中转站,再由独立 goroutine 拉取并遍历clients发送 - 性能影响:若不加缓冲,
make(chan string)是无缓冲 channel,一旦某个 client 写失败卡住,整个广播会停摆;建议用带缓冲的make(chan []byte, 100)
upgrader.CheckOrigin 不设为 true 就连不上?
开发阶段不放开跨域,浏览器前端用 new WebSocket("ws://localhost:8080/ws") 会直接报 Connection closed before receiving a handshake response。这不是协议错误,是 gorilla/websocket 默认拒绝非同源请求。
- 正确做法:明确允许跨域,但别用
return true上线——应校验r.Header.Get("Origin")是否在白名单内 - 调试时可临时写成:
CheckOrigin: func(r *http.Request) bool { return r.Header.Get("Origin") == "http://localhost:3000" || r.Header.Get("Origin") == "" } - 注意:某些代理(如 Nginx)可能不透传 Origin 头,此时需检查反向代理配置
如何从 HTTP 接口触发 WebSocket 推送?
这是“推送”的刚需场景:比如收到 POST /api/push,把消息发给所有在线用户。不能在 HTTP handler 里直接遍历 clients 并 WriteMessage,因为 clients 是 map,而 *websocket.Conn 非并发安全,且写操作可能阻塞。
立即学习“go语言免费学习笔记(深入)”;
- 安全做法:HTTP handler 只往
broadcastchannel 发消息,由已存在的handleMessages()goroutine 统一处理 - 示例接口逻辑:
func pushHandler(w http.ResponseWriter, r *http.Request) { var req struct{ Msg string } json.NewDecoder(r.Body).Decode(&req) broadcast <- []byte(req.Msg) // 注意:这里发的是 []byte,不是 string w.WriteHeader(http.StatusOK) } - 容易踩的坑:如果广播逻辑里用
client.WriteJSON(msg),但 msg 是string类型,会 panic;统一用[]byte或封装结构体更稳
真正难的不是“怎么发”,而是“发的时候连接还健在吗”。每个 *websocket.Conn 都要配心跳(PingHandler)、超时控制(SetReadDeadline)和异常捕获,否则 clients map 里会堆积大量已断开却未清理的连接,广播时反复报 use of closed network connection ——这个细节,90% 的入门示例都漏掉。










