Go中长连接需按HTTP长轮询、WebSocket、裸TCP三类区分配置:HTTP长轮询依赖服务端及时Flush响应头并调大Nginx超时;WebSocket须禁用默认心跳、降缓冲区、优化连接ID;TCP需分设读写Deadline、处理粘包、并发安全连接管理。

Go 里“长连”不是设个参数就完事的,得先分清你用的是哪种长连:HTTP 长轮询、WebSocket 还是裸 TCP。三者底层机制完全不同,混用配置会直接失效。
HTTP 长轮询:别碰 Transport 的 MaxIdleConnsPerHost
很多人一上来调 http.Transport.MaxIdleConnsPerHost 或 IdleConnTimeout,结果客户端还是频繁重连——因为长轮询根本不用复用连接,它只靠单个请求挂住不返回。
- 服务端必须显式
w.WriteHeader(http.StatusOK)+w.(http.Flusher).Flush(),否则客户端收不到响应头,永远卡在 CONNECTING - Nginx 默认
proxy_read_timeout 60s,超时会硬断连接,必须调大(比如300) - 客户端不能用
http.Client.Timeout控制整体耗时,它在响应头发出后就失效;要用context.WithTimeout构造 request,并在读 body 前检查ctx.Err() -
json.NewDecoder(resp.Body).Decode()不感知 context,一旦服务端不写完,就会死等;必须配合io.ReadFull或手动分段读 + 检查 ctx
WebSocket:别让每个连接都绑一个 timer
gorilla/websocket 默认启心跳和定时器,百万连接时 runtime.timer 会吃光 CPU。
- 禁用默认心跳:
conn.SetPongHandler(nil),自己用time.Ticker批量轮询活跃连接状态 - 把
ReadBufferSize和WriteBufferSize降到1024和512,够用且省内存 - 内网可信场景下,
Upgrader.CheckOrigin直接返回true,省掉字符串比较开销 - 连接 ID 用
uint64而非string作 map key,避免哈希和 GC 压力
TCP 长连接:超时控制必须分读/写两路
只设 SetKeepAlive(true) 没用,NAT 或中间设备照样断连;真正在意的是业务层可感知的“活”。
立即学习“go语言免费学习笔记(深入)”;
- 服务端对每个
net.Conn必须调conn.SetReadDeadline和conn.SetWriteDeadline,时间要略大于心跳间隔(比如心跳 30s,deadline 设 45s) - 客户端发心跳不能只靠
conn.Write,要启动独立 goroutine 读响应,出错立刻重连 - 重连必须指数退避(1s → 2s → 4s…),上限建议 30s,避免雪崩
- 粘包必须处理:推荐定长头(前 4 字节存 body 长度),读取用
io.ReadFull保整帧
连接管理:map 并发写 panic 是高频翻车点
直接把 *net.Conn 存 map[string]*net.Conn?运行起来必 panic:“concurrent map read and map write”。
- 用
map[uint64]*ConnMeta+sync.RWMutex,读多写少时性能损失极小 -
ConnMeta里必须嵌sync.Once和atomic.Bool,防止多个 goroutine 同时 close 同一连接 - 不要在
conn.Read()的 goroutine 里直接delete(m, id),应发信号给统一清理协程 - 定期扫描连接池(比如每 30s),踢掉
LastPingTime.Add(2 * heartbeatInterval).Before(time.Now())的连接
最易被忽略的其实是错误归因:看到连接断了,第一反应不是查 conn.Read 返回的 io.EOF 或 net.OpError,而是去调 Transport 参数——方向错了,越调越糟。


















