长连接管理器的核心职责是解决连接存活检测、连接复用控制和异常连接自动清理;需用sync.Map+心跳定时器实现,并避免依赖TCP Keepalive,须自建应用层轻量心跳与context控制goroutine生命周期。

长连接管理器的核心职责是什么
长连接管理器不是单纯维护一堆 net.Conn,而是要解决三个实际问题:连接存活检测、连接复用控制、异常连接自动清理。如果你只用 map[string]*net.Conn 存连接,没配心跳和超时,上线半天就会遇到 write: broken pipe 或 goroutine 泄漏。
用 sync.Map + 心跳定时器实现基础管理器
直接用 map 并发读写会 panic,必须用 sync.Map;但光有并发安全不够,客户端断连后连接对象还在内存里占着,得靠心跳驱动清理。
- 每个连接关联一个
*time.Timer,收到心跳重置,超时触发conn.Close()并从sync.Map中Delete - 避免在心跳 handler 里直接调
conn.Write—— 可能阻塞,改用带超时的conn.SetWriteDeadline+ 非阻塞写 - 连接 ID 建议用客户端传来的 token 或 IP+端口哈希,别用随机 UUID,否则重连无法复用已有连接
type ConnManager struct {
connections sync.Map // map[string]*managedConn
}
type managedConn struct {
conn net.Conn
timer *time.Timer
mu sync.RWMutex
}
为什么不能依赖 TCP Keepalive 做连接保活
SetKeepAlive 只能在内核层面探测链路层是否断开,对 NAT 超时、中间代理静默丢包、客户端进程卡死完全无效。真实场景中,90% 的“假在线”连接靠 TCP Keepalive 发现不了。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- Go 默认 keepalive 间隔是 2 小时(Linux 内核默认),远超业务容忍范围
- 必须自己实现应用层心跳,例如每 30 秒发一次
PING,5 秒内没收到PONG就标记为失效 - 心跳包要轻量,建议用固定长度二进制头(如 4 字节 magic + 1 字节 cmd)而非 JSON,减少解析开销
goroutine 泄漏的典型来源和规避方式
最常见的是为每个连接起一个 go readLoop(),但没做退出同步,连接关闭后 goroutine 还在等 conn.Read 返回。
- 用
context.WithCancel绑定连接生命周期,readLoop和writeLoop都监听 cancel signal - 关闭连接前先
cancel(),再conn.Close(),防止Read在 close 后才返回导致后续逻辑错乱 - 不要在
defer conn.Close()里关连接 —— 它只在函数退出时触发,而长连接的 handler 是常驻的
连接管理本身不复杂,难的是边界条件:客户端网络抖动时重复发心跳、重连请求撞上旧连接超时、心跳响应乱序。这些地方不加日志和计数器,出问题只能靠猜。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










