conn.writemessage()不能直接广播,因该方法非并发安全,多goroutine调用必panic;且网络异常时会无限阻塞,拖垮流程。必须为每个连接配独立writepump goroutine串行写入,并通过带缓冲的send channel与hub协同实现安全广播。

为什么 conn.WriteMessage 不能直接广播
因为 WebSocket 连接是单对单的,conn.WriteMessage 只能往当前连接写数据,没有内置“群发”能力。你拿到的每个 *websocket.Conn 都是独立对象,彼此不感知。强行遍历所有连接并逐个 WriteMessage 看似可行,但会立刻暴露并发问题:多个 goroutine 同时调用同一个 conn.WriteMessage 会 panic,错误信息通常是 concurrent write to websocket connection。
所以必须引入同步机制——不是锁整个 map,而是为每个连接维护独立的写队列 + 单独的 writer goroutine。
如何安全地实现广播:用 hub + client + writePump
标准做法是拆三层:
-
hub:全局中心,用map[*client]bool管理在线客户端,接收来自各client的消息,再分发给所有client的sendchannel -
client:封装一个连接,含*websocket.Conn、send(chan []byte)、hub引用 -
writePump:每个client启一个 goroutine,从sendchannel 读消息,再调用conn.WriteMessage—— 此时写操作是串行的,不会并发冲突
关键点:send channel 必须有缓冲(比如 make(chan []byte, 32)),否则广播压力大时容易阻塞 hub;writePump 要监听 conn.Close 或 channel 关闭,及时退出。
func (c *client) writePump() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
c.conn.Close()
}()
for {
select {
case message, ok :=
<h3>广播时该用 <code>TextMessage</code> 还是 <code>BinaryMessage</code>
</h3>
<p>聊天场景下,99% 用 <code>TextMessage</code> 就够了。WebSocket 协议本身不强制编码,但浏览器 <code>WebSocket</code> 对象只支持发送字符串或 <code>ArrayBuffer</code>,Go 的 <code>gorilla/websocket</code> 库也默认按 UTF-8 解析 <code>TextMessage</code>。</p>
- 如果消息是 JSON 字符串(如
{"user":"alice","text":"hi"}),必须用TextMessage,否则浏览器端onmessage拿到的是乱码 ArrayBuffer -
BinaryMessage适合传图片 base64 片段、protobuf 序列化数据等,但需前后端约定好解析逻辑,且浏览器 JS 侧要手动处理ArrayBuffer - 别在
TextMessage里塞非 UTF-8 数据(比如 raw bytes),会触发websocket.ErrCloseSent或静默截断
为什么广播卡顿?检查 SetWriteDeadline 和 ReadDeadline
没设写超时,某个客户端网络卡住或假死,writePump 会在 c.conn.WriteMessage 处永久阻塞,导致整个 channel 积压,后续广播全部延迟。同理,没设读超时,恶意客户端不发 pong,连接永远挂着,hub 里积累大量僵尸 client。
- 务必在
conn建立后立刻设置:conn.SetWriteDeadline(time.Now().Add(writeWait))(writeWait推荐 10s) -
readPump中每次ReadMessage前更新读 deadline:conn.SetReadDeadline(time.Now().Add(pongWait)) - 收到
Ping时主动回Pong,避免被误判断连
deadline 不是可选项,是生产环境保命线。本地测试看不出问题,一上公网,NAT、移动网络抖动、客户端切后台,立刻暴露。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











