贝利信息

Go如何优雅关闭网络连接_Go网络资源释放策略

日期:2026-01-16 00:00 / 作者:P粉602998670
conn.Read() 返回 0 字节且 err 为 io.EOF 时必须立即关闭连接,否则会导致空转和 CPU 暴涨;需同时检查 n == 0 和 err == io.EOF 作为终止信号,而非仅判断 err != nil。

conn.Read() 返回 0 字节时必须关闭连接

这是最容易被忽视却最致命的坑:当 conn.Read() 返回 read_len == 0 且错误为 io.EOF(或有时无错误),说明对端已发送 FIN,连接进入半关闭状态。此时若继续循环调用 Read(),会立即返回 0 + nil,造成空转、CPU 暴涨。

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 检测远远不够。

超时值要小于中间设备的 idle timeout,否则永远等不到断开信号。

并发场景下避免重复 Close 和 goroutine 竞态

多个 goroutine 共享一个 *net.TCPConn 时,谁该关?什么时候关?不加协调极易 panic(close of closed channel 类似逻辑也适用于连接)。

var once sync.Once
func safeClose(conn 

net.Conn) { once.Do(func() { conn.Close() }) }

服务整体退出时:先停 Listener,再等连接 Drain

程序收到 SIGINTSIGTERM 后,不能直接 os.Exit()。要分两步:停止接受新连接,再等待已有连接自然结束(或超时强制终止)。

如果某些连接卡死(比如客户端不读响应),必须设超时(如 10 秒),否则整个 shutdown 会被拖住。