必须显式调用conn.Close()释放fd,避免too many open files;HTTP需defer resp.Body.Close();Accept连接应在goroutine入口defer关闭;CloseWrite()不等价于Close(),慎用半关闭。

连接未关闭导致 fd 耗尽怎么办
Go 的 net.Conn 是操作系统文件描述符(fd)的封装,不显式关闭会持续占用,直到 GC 触发 finalizer —— 但这个时机不可控,且 fd 可能早于 GC 就被系统限制打满(常见报错:too many open files)。尤其在高并发短连接场景(如 HTTP client、TCP 扫描器、数据库连接池外手动 dial),这个问题暴露得极快。
必须在业务逻辑结束、数据收发完毕后立刻调用 conn.Close()。不要依赖 defer 放在函数入口——如果连接建立失败或早期 return,defer 会失效;更不能只在 error 分支 close。
- 正确做法:在
if err != nil后立即 close(如果 conn 已非 nil),再 return - 推荐模式:用
if conn != nil { defer conn.Close() }包裹整个处理块,但需确保 conn 确实被成功赋值 - 注意:
conn.Close()是幂等的,重复调用无副作用,但多次 defer 同一 conn 会导致 panic(“close of closed channel” 类似逻辑不适用,但代码冗余)
HTTP client 中 resp.Body 忘记 Close 的后果
这是 Go 新手最常踩的坑:http.Get 或 client.Do 返回的 *http.Response,其 resp.Body 是一个 io.ReadCloser,底层复用 TCP 连接。不调用 resp.Body.Close(),不仅 fd 不释放,还会阻塞连接复用(keep-alive),导致后续请求新建连接,加重服务端压力。
即使你只读前几个字节、或直接忽略 body,也必须 close。
立即学习“go语言免费学习笔记(深入)”;
- 安全写法:
defer resp.Body.Close()—— 但要放在if resp != nil之后,避免 resp 为 nil 时 panic - 错误写法:
if resp.StatusCode == 200 { ... }后才 close —— 非 200 响应体仍需关闭 - 特殊情况:用
ioutil.ReadAll(resp.Body)(Go 1.16+ 推荐io.ReadAll)后仍要 close,因为 ReadAll 不会自动关 body
Listener.Accept() 循环里如何防泄漏
net.Listener 的 Accept() 返回新连接,每次返回的 conn 都需独立管理。典型错误是只在 goroutine 内部 close,但 goroutine 因 panic 或超时未执行完,conn 就漏了。
关键原则:每个 conn 的生命周期必须有明确 owner,且 owner 保证 close。
- 标准模式:每 accept 一个 conn,就启一个 goroutine 处理,并在该 goroutine 入口 defer close
- 加超时控制时,用
conn.SetReadDeadline/SetWriteDeadline,而非靠外部 context cancel —— 因为 cancel 不会自动关 conn - 若用
context.WithTimeout控制 handler,需在 defer 前判断 conn 是否已关闭(conn.RemoteAddr() == nil不可靠,建议用atomic标记或 channel 协同)
为什么 tcpConn.CloseWrite() 不等于关闭连接
net.Conn 的 CloseWrite()(对应 TCP 的 FIN 发送)仅关闭写方向,连接仍可读;而 Close() 是双向关闭(发送 FIN + 关闭读缓冲)。误用 CloseWrite() 代替 Close(),会导致对端一直等剩余数据,连接长时间处于 FIN_WAIT2 或 CLOSE_WAIT 状态。
除非你明确需要半关闭语义(如 HTTP/1.0 的 chunked 请求后只关写),否则一律用 conn.Close()。
- 常见误用场景:实现简单协议时,发完数据就调
conn.CloseWrite(),然后等着读响应,却忘了后续还要conn.Close() - 验证方式:用
lsof -i :port或ss -tuln观察连接状态,大量CLOSE_WAIT通常意味着本端没 close 读端 - 注意:
CloseWrite()在 Windows 上可能 panic,因底层 socket 不支持半关闭
真正难的不是记住要 close,而是厘清谁该负责 close、在哪个确切时机 close。连接泄漏往往藏在 error 分支、超时路径、panic 恢复之后——这些地方最容易被忽略。











