websocket流式响应失败需检查:1. 禁止用http.responsewriter发送数据,必须用*websocket.conn;2. 避免逐token发送,应缓冲后调用writemessage;3. 设置读超时与心跳检测防断连;4. 推理与推送分离并设超时。

WebSocket连接建立后收不到AI流式响应?检查 http.ResponseWriter 是否被提前关闭
Go 的 http.ServeHTTP 处理函数返回即代表 HTTP 响应生命周期结束,若在此之后尝试向 http.ResponseWriter 写入数据(比如误用它做流式输出),会触发 http: response.WriteHeader on hijacked connection 错误。WebSocket 必须通过 gorilla/websocket 或 golang.org/x/net/websocket 的 *websocket.Conn 实例通信,而非原生 ResponseWriter。
实操建议:
- 用
upgrader.Upgrade()升级连接前,确保没有调用过w.WriteHeader()或任何w.Write() - 升级成功后立即丢弃
http.ResponseWriter和*http.Request,只保留*websocket.Conn - 若使用
gorilla/websocket,注意Upgrader.CheckOrigin默认拒绝非同源请求,开发时可临时设为func(r *http.Request) bool { return true }
流式生成时消息粘连或截断?必须手动控制 conn.WriteMessage() 的调用粒度
AI 模型(如 Llama、Qwen)通常以 token 或 chunk 为单位输出,但直接对每个小片段都调用 conn.WriteMessage(websocket.TextMessage, []byte(chunk)) 容易触发 TCP 小包合并(Nagle 算法)或 WebSocket 帧碎片化,导致前端收到不完整 JSON 或乱序字符串。
实操建议:
- 避免逐 token 发送;改用缓冲策略:累积 2–5 个 token 后再发一次,或按字符数(如每满 16 字符)触发发送
- 每次
WriteMessage前确保数据是合法 UTF-8,否则前端JSON.parse()会失败;可用utf8.Valid()校验 - 不要在 goroutine 中无锁并发调用
WriteMessage——*websocket.Conn的写操作不是并发安全的,需加mutex.Lock()或用带缓冲 channel 统一调度
客户端断连后服务端还在推送?靠 conn.SetReadDeadline() + 心跳检测维持连接活性
WebSocket 连接可能因网络抖动、NAT 超时或客户端页面关闭而静默中断,但服务端仍持续调用 WriteMessage,最终阻塞在 socket write 或触发 write: broken pipe panic。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
实操建议:
- 启用读超时:
conn.SetReadDeadline(time.Now().Add(30 * time.Second)),并在循环中定期调用conn.ReadMessage()(哪怕只收 ping) - 配合
conn.SetPongHandler()自动响应 ping,避免因未处理 pong 导致连接被关 - 在流式生成 goroutine 中,每次
WriteMessage后检查conn.Close() == nil或捕获websocket.IsUnexpectedCloseError类错误,及时退出
模型推理耗时长导致 WebSocket 连接超时?把推理和推送拆到不同 goroutine 并设置合理超时
若在 Upgrade 后直接调用 llm.Generate(),整个 HTTP handler 会被阻塞,而反向代理(如 Nginx)默认 60 秒超时,会主动断开连接,前端看到 WebSocket is closed before the connection is established。
实操建议:
- 用
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)包裹模型调用,防止无限等待 - 启动独立 goroutine 执行推理,主 goroutine 专注监听
conn状态并转发结果;两者通过chan string或chan []byte通信 - 若模型支持 streaming 接口(如 Ollama 的
/api/chat?stream=true),优先用http.Client流式读取其响应体,再转推至 WebSocket,避免本地内存积压
真正难的不是“怎么发”,而是“发的过程中连接突然没了怎么办”——重试逻辑要区分是网络闪断(可重连)、用户关闭页面(该清理资源),还是模型崩了(该记录日志)。这些边界情况不会在 demo 里出现,但上线后每天都在发生。










