用net/http启服务收不到推送,需检查连接复用:服务端sse须用http.flusher.flush(),客户端设maxidleconnsperhost=1;websocket需管控context生命周期、心跳及isclosed检查;广播用sync.map快照+独立写协程;生产延迟需优化dns/tls建连。

用 net/http 启服务但收不到推送?检查连接是否被复用
Go 的 HTTP 客户端默认启用连接复用(Keep-Alive),而很多推送场景(比如长轮询、SSE)依赖连接不关闭。服务端写完响应后没显式 Flush(),或者客户端提前复用连接,就会卡住或丢数据。
- 服务端用
http.ResponseWriter推送 SSE 时,必须调用flusher, ok := w.(http.Flusher)判断并flusher.Flush() - 客户端用
http.Client时,设Transport.MaxIdleConnsPerHost = 1避免复用到旧连接 - 别用
io.Copy(w, r.Body)直接透传,它不触发 flush;要分块写 + 显式 flush
WebSocket 连接频繁断开?注意 context 生命周期和心跳超时
Go 标准库没有内置 WebSocket,主流用 gorilla/websocket,但它对连接生命周期完全交由使用者控制。常见问题是:读协程因超时不退出,写协程还在发心跳,底层 TCP 连接已断,导致 panic 或 goroutine 泄漏。
- 每个连接启动两个协程:一个
conn.ReadMessage()带context.WithTimeout(),另一个定时conn.WriteMessage(websocket.PingMessage, nil) -
WriteMessage()调用前必须检查conn.IsClosed() == false,否则会 panic - 服务端
Upgrader.CheckOrigin默认拒绝非同源请求,测试时记得设为func(r *http.Request) bool { return true }
消息广播性能上不去?别直接遍历所有 *websocket.Conn
用 map 存连接、for-range 广播是初学者最常用写法,但并发写 map 会 panic,加锁又成瓶颈。真实压测下,1000 连接广播一次就可能卡住主线程。
- 用
sync.Map存连接,但只用于注册/注销;广播时先LoadAll()拷贝快照,再遍历写 - 给每个连接配独立写协程 + channel,业务逻辑往 channel 发消息,避免阻塞广播主流程
- 批量写之前检查
conn.WriteBufferPool是否被自定义——gorilla v1.5+ 默认用sync.Pool,别自己 new []byte
上线后推送延迟高?确认 DNS 解析和 TLS 握手没拖慢建连
本地跑得飞快,一上生产就延迟几秒,大概率是 DNS 或 TLS 层问题。Go 的 http.Transport 默认开启 GetConn 等待,但没暴露超时钩子,容易卡在解析或握手阶段。
- 给
http.Transport设.DialContext,包装net.Dialer并加Timeout和KeepAlive - TLS 场景下,务必设
TLSClientConfig: &tls.Config{InsecureSkipVerify: false},跳过验证虽快但不安全,且某些证书链缺失反而更慢 - 用
curl -v或tcpdump看实际建连耗时分布,别只信日志里的“处理耗时”
真正难的不是推一条消息,而是连接状态管理、错误恢复和资源回收——比如一个断连的 WebSocket 连接,它的读协程是否已退出?channel 是否已 close?buffer 是否被释放?这些细节不盯住,压测时崩得毫无征兆。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











