单机版聊天室用gorilla/websocket足够,但需处理断连、广播、状态维护等核心问题:读写必须同goroutine顺序执行,设read/writedeadline防卡死,用channel+writer goroutine实现并发写,clients用sync.map或加锁保护并以id为键,广播前检查连接状态并及时清理,cors和响应头配置不当会导致handshake失败。

单机版聊天室用 gorilla/websocket 就够了,但别直接照抄示例代码——它默认不处理连接断开、不广播消息、不维护客户端状态,跑起来看似能用,实际一刷新就丢人。
为什么 upgrader.Upgrade() 后必须立刻读写,不能直接扔进 goroutine?
常见错误是把 conn 交给 goroutine 后就不管了,结果客户端一断开,conn.ReadMessage() 会卡死或 panic。Go 的 WebSocket 连接不是线程安全的,且 ReadMessage() 和 WriteMessage() 都是阻塞调用。
- 必须在同一个 goroutine 里顺序处理读和写,否则可能触发
websocket: write closed错误 - 读操作建议用
SetReadDeadline()配合超时控制,避免 goroutine 泄漏 - 写操作要用
conn.SetWriteDeadline(),否则网络抖动时容易卡住整个连接 - 如果真要并发写(比如广播),得用带缓冲的 channel + 单独的 writer goroutine,而不是直接多处调用
WriteMessage()
clients map 为什么不能直接用 map[*websocket.Conn]bool?
看起来简单,但这样会导致内存泄漏和并发 panic。每个 *websocket.Conn 是指针,GC 不会自动回收已断开但没从 map 中删掉的连接;同时 map 本身不是并发安全的。
- 必须用
sync.Map或加sync.RWMutex保护,否则高并发下会 crash - 键值不能是
*websocket.Conn,应该用自增 ID 或 UUID 作 key,Conn 存为 value,方便后续按 ID 主动踢人或发私信 - 每次
defer delete(clients, clientID)要放在 upgrade 成功之后、读循环开始之前,不然断连时删不掉 - 记得在
ReadMessage()返回 error 后显式调用conn.Close(),否则连接资源不会释放
广播消息时,为什么不能对每个 conn.WriteMessage() 做 recover()?
很多人用 defer+recover 捕获单个写失败,结果发现消息只发了一半就停了——因为写失败后连接已损坏,继续往里面写只会反复报错,还拖慢其他客户端。
- 每个写操作前先检查
conn.IsClosed()(需自己实现或用conn.WriteControl()探活) - 写失败时立即
delete()并conn.Close(),不要尝试重试 - 广播逻辑必须是非阻塞的:把消息塞进每个 client 的专属 write channel,由独立 goroutine 消费,避免一个卡住拖垮全部
- 如果用
sync.Map存 clients,遍历时要用LoadAndDelete()配合判断,不能边遍历边删
前端连不上时,websocket: bad handshake 通常不是端口问题
这个错误八成是因为服务端没正确设置 CORS 或响应头,浏览器直接拒绝升级请求,根本到不了 WebSocket 协议层。
-
upgrader.CheckOrigin = func(r *http.Request) bool { return true }只适合开发,生产必须校验 Origin - 确保
http.ServeMux没被其他中间件(如 gzip、logging)提前消费 request body - 前端 new WebSocket() 地址必须和后端
http.ListenAndServe()绑定地址一致,HTTPS 页面不能连 HTTP ws:// - Chrome 控制台 Network 标签页里看 upgrade 请求的 Response Headers,确认有
Upgrade: websocket和Connection: Upgrade
真正麻烦的从来不是“怎么连上”,而是“连上之后怎么不死、不卡、不丢、不炸”。连接数一过百,那些没设 deadline、没做并发保护、没清理 dead conn 的代码就会开始露馅。别急着加功能,先把断连恢复、心跳保活、写队列压测跑通再说。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











