net.Conn关闭后读返回io.EOF(正常结束),写返回“write: broken pipe”或“use of closed network connection”;需区分处理,不可将io.EOF当异常panic。

Go 中 net.Conn 关闭后继续读写会触发什么错误
调用 conn.Read() 或 conn.Write() 时,若连接已被对端关闭或本地已调用 conn.Close(),会立即返回 io.EOF(读)或 write: broken pipe/use of closed network connection(写)。注意:io.EOF 是正常结束信号,不是异常;而 write: broken pipe 表明对端已 RST,use of closed network connection 则是本地重复操作。
常见误判是把 io.EOF 当作错误 panic,结果导致合法断连被当成崩溃。正确做法是显式区分:
if err == io.EOF {
// 正常关闭,清理资源
return
}
if err != nil {
// 真正的异常:超时、中断、协议错误等
log.Printf("read error: %v", err)
return
}
如何安全地复用 net.Conn 并避免 use of closed network connection
Go 的 net.Conn 不支持重连或自动恢复,一旦 Close() 就不可再用。很多开发者试图在 defer 中关闭、又在后续逻辑里继续写,或者在 goroutine 里并发读写同一 conn,都会触发该错误。
关键约束有三点:
立即学习“go语言免费学习笔记(深入)”;
- 每个
net.Conn实例只能被一个 goroutine 安全读,另一个 goroutine 安全写(即读写分离),或统一由单个 goroutine 串行处理 - 关闭前确保所有读写操作已退出 —— 建议用
sync.WaitGroup或context.WithCancel协同控制生命周期 - 不要在
defer conn.Close()后还调用conn.Write();如需响应,应在Close()前完成
net.DialTimeout 和 context.WithTimeout 哪个更适合控制连接建立阶段错误
net.DialTimeout 只能控制 TCP 握手阶段,无法覆盖 TLS 握手、认证、协议协商等后续耗时环节;而 context.WithTimeout 可贯穿整个连接初始化流程(包括 tls.ClientConn、自定义 Handshake 等),语义更完整、可控性更强。
推荐写法:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
conn, err := (&net.Dialer{}).DialContext(ctx, "tcp", addr, "")
if err != nil {
// 覆盖:timeout、no route to host、connection refused、TLS timeout 等全部情况
return nil, err
}
如果硬要用 DialTimeout,记得它不兼容 http.Transport 或 grpc.Dial 等高级封装,容易漏控。
HTTP Server 中如何捕获连接中断而不 panic
http.Server 默认会在客户端提前断开时记录 http: abort Handler 日志,但不会 panic。真正危险的是你在 handler 里执行长耗时操作(如数据库查询、文件写入)后才写 response,此时连接可能早已断开,WriteHeader 或 Write 会返回 broken pipe 或 connection reset by peer。
必须主动检查:
- 每次
resp.WriteHeader()和resp.Write()后检查err - 使用
resp.(http.Hijacker)时更要小心,hijack 后的 conn 完全脱离 http.Server 管理,错误需自行处理 - 对流式响应(如 SSE、chunked),建议定期调用
resp.(http.Flusher).Flush()并检查错误,及时退出
连接异常往往藏在 write 阶段,而不是 listen 或 accept 阶段 —— 这点最容易被忽略。











