必须在 HTTP handler 中调用 Upgrader.Upgrade() 以接管 TCP 连接,否则连接关闭导致握手失败;升级前不可向 ResponseWriter 写任何内容,升级后需用独立 writePump goroutine 和 send channel 保障并发安全,并设 WriteDeadline 防阻塞。

直接用 gorilla/websocket,别碰标准库 net/http 或已归档的 golang.org/x/net/websocket —— 后者不维护、无并发保护,前者压根没 WebSocket 实现。
为什么 Upgrader.Upgrade() 必须在 handler 里调,不能注册完路径就完事
因为 gorilla/websocket 需接管 TCP 连接生命周期,而 http.HandleFunc 返回后 HTTP 连接立刻关闭。如果只注册路径不显式升级,浏览器会报 ERR_CONNECTION_CLOSED,服务端日志出现 websocket: bad handshake。
-
Upgrader.Upgrade()前绝不能往w http.ResponseWriter写任何东西(包括空格、换行、log.Println输出到响应体) - 升级成功后,
w就失效了,再写会 panic:http: multiple response.WriteHeader calls - 必须用
http.ServeMux或直接传给http.Server,handler 中完成升级逻辑
连接一建立就 panic 或收不到消息?先查 CheckOrigin 和 WriteMessage 并发
开发阶段最常见两个拦路虎:跨域被拒导致 403,或多个 goroutine 同时调 conn.WriteMessage() 引发 concurrent write to connection panic。
-
Upgrader.CheckOrigin默认拒绝所有非同源请求;临时绕过设为func(r *http.Request) bool { return true },但上线前必须换成白名单校验(如匹配r.Header.Get("Origin")) -
*websocket.Conn的读写均非并发安全;每个连接必须配一个send chan []byte(建议缓冲大小 32–256),由唯一writePumpgoroutine 从 channel 取值并调conn.WriteMessage() - 广播时别直接遍历 map 调
WriteMessage(),而是往每个 client 的sendchannel 发消息
连接“挂着”却收不到消息?本质是假在线 + 没设 deadline
TCP 连接没断,但客户端早已失联(锁屏、NAT 超时、拔网线),此时 WriteMessage() 会无限阻塞,卡死整个广播循环,所有用户消息积压。
立即学习“go语言免费学习笔记(深入)”;
- 每次调
conn.WriteMessage()前必须设conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) - 超时返回
net.ErrWriteTimeout或io.EOF时,立即delete(clients, conn)并conn.Close() - 服务端要主动发
websocket.PingMessage(比如每 25–30 秒),不能只等客户端 ping;客户端自动回 pong,服务端在SetPongHandler里重置ReadDeadline -
readPump和writePump必须成对启停:writePump结束前先close(c.send),readPump收到websocket.CloseMessage后也要触发清理
最容易被忽略的是:conn.IsClosed() 不可靠,它只是本地状态标记;真正判断连接是否可用,得靠 WriteMessage() 的 error + WriteDeadline 超时组合验证。别信“连着就是活的”。


















