gorilla/websocket是首选,因标准库net/http仅支持HTTP握手,不提供WebSocket帧解码、心跳等完整功能;硬写易出错且难应对生产问题。
gorilla/websocket 是首选而不是标准库 net/http
Go 标准库的 net/http 本身不提供 WebSocket 协议解析能力,仅能处理 HTTP 握手阶段;真正完成升级(Upgrade)、帧解码、心跳、连接状态管理等,必须依赖成熟实现。官方文档也明确建议使用 gorilla/websocket 或 gobwas/ws 等第三方包。
直接用标准库硬写 WebSocket 会陷入字节流解析、掩码校验、控制帧处理等底层细节,极易出错且无法应对生产环境的连接中断、重连、并发读写冲突等问题。
http: response.WriteHeader called multiple times
常见错误是:在调用 upgrader.Upgrade() 前或后,意外触发了 http.ResponseWriter 的其他写操作(如 w.WriteHeader()、w.Write())。该函数内部已自动完成
HTTP 状态切换和响应头写入,任何前置或后续的显式响应操作都会导致 panic。
upgrader.Upgrade(),且它必须是该 handler 中对 http.ResponseWriter 的唯一写操作http.ResponseWriter 和 *http.Request 不再可用,后续通信全部通过返回的 *websocket.Conn 进行func chatHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
// 不要在这里调用 http.Error() 或 w.WriteHeader()
return
}
defer conn.Close()
// 后续所有读写都走 conn.ReadMessage() / conn.WriteMessage()
}
如何安全地广播消息而不引发 concurrent write to websocket connection
*websocket.Conn 的读写方法 **不是 goroutine 安全的**。多个 goroutine 同时调用 WriteMessage() 会导致 panic。聊天室典型场景中,一个连接可能同时被“新用户加入通知”“群聊消息”“系统踢出指令”等多个逻辑触发写操作,必须串行化。
立即学习“go语言免费学习笔记(深入)”;
conn.WriteMessage()
conn.WriteMessage();应把消息推入各连接的写 channelmake(chan []byte, 32)),防止发送方阻塞;但缓冲满时需考虑丢弃或限流,否则内存泄漏type Client struct {
conn *websocket.Conn
send chan []byte // 所有写操作必须经此 channel
}
func (c *Client) writePump() {
defer c.conn.Close()
for message := range c.send {
if err := c.conn.WriteMessage(websocket.TextMessage, message); err != nil {
break
}
}
}
WebSocket 连接断开不会立即触发 conn.ReadMessage() 返回 error —— 它可能卡在阻塞读,或返回过期的 pong 响应。仅靠读错误判断不可靠。必须结合 SetReadDeadline()、SetPingHandler() 和主动心跳检测。
conn.SetReadDeadline(time.Now().Add(pingPeriod))
conn.SetPingHandler(func(appData string) error { conn.SetReadDeadline(time.Now().Add(pingPeriod)); return nil }),确保每次收到 ping 就刷新读超时pong(实际由库自动处理),并监听 conn.ReadMessage() 的 error;若超时或 EOF,说明连接已失效,应从全局 client map 中删除该连接漏掉 deadline 设置或 ping handler,会导致僵尸连接长期滞留,内存与 goroutine 泄漏会随时间恶化。