WebSocket客户端连接需用http://或https://前缀,gorilla/websocket自动升级协议;*Conn非并发安全,须配独立写协程;广播应批量聚合、预序列化并检查连接状态;需主动关闭、设读写deadline防泄漏与僵死。

WebSocket 连接建立失败:dial tcp: lookup ws:// 时出错
Go 原生不支持 ws:// 或 wss:// 协议直连,gorilla/websocket 的 websocket.Dial 只接受标准 HTTP URL(如 http://localhost:8080/ws),但你写了 ws://... 就会报 lookup ws://: no such host。
常见错误现象:用浏览器访问 ws://localhost:8080/ws 没问题,但 Go 客户端代码里写 websocket.Dial("ws://...") 直接 panic。
- 客户端连接必须用
http://或https://前缀,协议升级由 WebSocket 库自动处理 - 服务端路由注册仍用普通 HTTP handler,比如
http.HandleFunc("/ws", wsHandler) - 浏览器前端可照常用
new WebSocket("ws://..."),和服务端是否用 Go 无关
并发读写 panic:concurrent write to websocket connection
gorilla/websocket 的 *Conn 不是并发安全的——多个 goroutine 同时调用 WriteMessage 会直接 panic。
典型场景:一个用户上线后,后台启动心跳 goroutine 定期发 ping,同时业务逻辑又在另一个 goroutine 里推送消息,两者撞上就崩。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 必须为每个连接配一个独占的写协程(writePump),所有发消息都走 channel 投递到它手里
- 读协程(readPump)只负责收消息和关闭通知,不直接写
- 别在 handler 里直接
conn.WriteMessage(...),尤其别在 HTTP handler 返回前调用
消息广播性能卡在 O(n):每次遍历所有 conn 调用 WriteMessage
简单循环遍历在线连接列表发消息,在几百人同时在线时,单次广播延迟飙升,CPU 花在锁竞争和系统调用上。
根本原因:每个 WriteMessage 都触发一次底层 socket write,且 *Conn 内部有 mutex 保护写缓冲区。
- 用单个写协程 + channel 批量聚合消息,避免高频小包
- 广播前先检查 conn 是否还活跃(
conn.IsClosed()或监听conn.Close通道) - 不要在广播循环里做 JSON marshal,提前序列化好再投递
- 考虑用
conn.SetWriteDeadline防止单个卡住拖垮全局
HTTP handler 中没调用 conn.Close 导致资源泄漏
很多人只记得 defer conn.Close(),但忘了它得在 handler 函数退出前执行;如果连接异常中断(比如用户关浏览器),handler 可能卡在 ReadMessage 上永远不返回,defer 根本不触发。
更隐蔽的问题:用了 context.WithTimeout 包裹 handler,但 conn.ReadMessage 不感知 context,照样阻塞。
- 必须在 readPump 里监听
conn.Close通道或捕获io.EOF/websocket.CloseMessage - 主动关闭前调用
conn.WriteMessage(websocket.CloseMessage, ...),再conn.Close() - 用
conn.SetReadLimit和SetReadDeadline防止恶意大包或长连接僵死
真正难的不是写通消息收发,而是让成百上千连接稳住不掉、不卡、不漏内存。每条连接背后是 goroutine、channel、socket 缓冲区和 GC 压力,漏掉任何一个 close 或 deadline,跑两天就见分晓。

















