setreaddeadline必须每次读成功后重置,因为它只对下一次read()生效,而非永久设置;若不重置,后续读操作将因超时返回io.eof或阻塞报错,导致连接异常断开。

net.Conn 本身不提供长连接语义,所谓“长连接”是业务层主动维持的结果——不关连接、防超时断连、心跳保活、及时回收异常 goroutine。靠框架或默认配置撑不住真实场景。
为什么 SetReadDeadline 必须每次读成功后重置
很多人把 SetReadDeadline 放在循环外,以为设一次就管全程。结果是:第一次读完没重置,第二次 Read() 直接因超时返回 io.EOF 或阻塞后报错,连接悄无声息断开。
-
SetReadDeadline()只对下一次Read()生效,不是永久设置 - 心跳包(如
"ping")和业务数据都算“有效读”,只要收到就得立刻重置 deadline - 服务端和服务端都要做:服务端重置读超时;客户端发完
"ping"后必须调SetReadDeadline等"pong",否则无法感知对端掉线 - 别用
time.Sleep定时发 ping 就完事——没配对的读超时,等于没心跳
goroutine 泄漏比连接泄漏更致命
每个连接起一个读 goroutine + 一个写 goroutine 是常见模式,但一旦连接已断而写 goroutine 还在往 conn.Write() 发数据,就会卡死并持续占用内存和 fd。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 写操作必须配合
SetWriteDeadline,超时即退出,不能无限制等待 - 读 goroutine 退出时,应通过
chan或context.WithCancel通知写 goroutine 停止 - 避免在
defer conn.Close()外部启动写 goroutine——Close()不会自动中断正在阻塞的Write() - 用
sync.WaitGroup跟踪活跃 goroutine 数,重启/关闭时可等待它们自然退出
心跳内容与协议设计直接影响连接存活率
只发 "ping"/"pong" 字符串看似简单,但在 NAT、SLB、运营商网关等中间设备面前容易失效。
- 纯文本心跳易被 DPI(深度包检测)识别并干扰,建议加简单校验字段,如
{"type":"ping","ts":1720941360,"seq":123} - 心跳间隔要小于中间设备空闲回收阈值(通常 5–15 分钟),推荐设为 30–45 秒
- 服务端收到心跳必须回包,且回包也需触发客户端侧的
SetReadDeadline重置——单向心跳无效 - 不要复用业务缓冲区处理心跳:心跳解析失败不能影响主协议逻辑,建议独立小 buffer 或预解析头字段
并发连接数上万时,系统级限制比 Go 代码更先见顶
Go 能轻松跑 10w+ goroutine,但 net.Conn 底层依赖文件描述符(fd),Linux 默认单进程最多 1024 个 fd,不调就卡在几千连接。
- 运行前执行
ulimit -n 65535,并在 systemd service 文件里配LimitNOFILE=65535 - 内核参数要调:
net.core.somaxconn(全连接队列)、net.ipv4.tcp_max_syn_backlog(半连接队列)至少设到 65535 -
net.Conn对象本身不占大内存,但每个连接的 socket buffer、goroutine 栈(默认 2KB)、TLS 加密上下文(如启用)会快速累积 - 别迷信“goroutine 很轻”——10w 连接 ≈ 200MB 栈内存(未优化),务必用
sync.Pool复用[]byte缓冲区
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










