

新闻资讯
技术教程conn.Read() 返回 0 字节且 err 为 io.EOF 时必须立即关闭连接,否则会导致空转和 CPU 暴涨;需同时检查 n == 0 和 err == io.EOF 作为终止信号,而非仅判断 err != nil。
这是最容易被忽视却最致命的坑:当 conn.Read() 返回 read_len == 0 且错误为 io.EOF(或有时无错误),说明对端已发送 FIN,连接进入半关闭状态。此时若继续循环调用 Read(),会立即返回 0 + nil,造成空转、CPU 暴涨。
err != nil 就 break,却忽略 read_len == 0 的语义read_len == 0 和 err == io.EOF 一起视为连接终止信号,立刻 conn.Close()
io.EOF 不是异常,是正常关闭流程的一部分;而 "broken pipe" 或 "connection reset by peer" 才是异常断连,也需关闭func handleConn(conn net.Conn) {
defer conn.Close()
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if n == 0 || err == io.EOF {
log.Println("对端关闭连接")
return
}
if err != nil {
log.Printf("读取失败: %v", err)
return
}
// 处理 buf[:n]
}
}
NAT、防火墙、负载均衡器常在空闲 30–300 秒后静默 kill 连接,而 Go 程序毫无感知,后续写入直接 panic 或阻塞。仅靠 io.EOF 检测远远不够。
conn.SetReadDeadline() 和 conn.SetWriteDeadline() 必须显式设置,不能依赖系统默认reconnectionDelay: 1000, reconnectionDelayMax: 5000)超时值要小于中间设备的 idle timeout,否则永远等不到断开信号。
多个 goroutine 共享一个 *net.TCPConn 时,谁该关?什么时候关?不加协调极易 panic(close of closed channel 类似逻辑也适用于连接)。
sync.Once 包裹 conn.Close(),确保只执行一次context.Context 控制生命周期,读/写 goroutine 都监听 ctx.Done(),收到信号后主动退出并触发关闭conn.Close() —— 如果连接已被其他 goroutine 关闭,defer 会 panicvar once sync.Once
func safeClose(conn net.Conn) {
once.Do(func() {
conn.Close()
})
}
程序收到 SIGINT 或 SIGTERM 后,不能直接 os.Exit()。要分两步:停止接受新连接,再等待已有连接自然结束(或超时强制终止)。

listener.Close() 会让阻塞的 Accept() 立即返回错误(如 "use of closed network connection"),主循环可快速退出sync.WaitGroup,Accept() 后 wg.Add(1),处理完 wg.Done()
wg.Wait() 等待所有连接处理完毕,再退出如果某些连接卡死(比如客户端不读响应),必须设超时(如 10 秒),否则整个 shutdown 会被拖住。