Go中net.Dial失败时,需通过err.(net.Error).Temporary()或Timeout()判断是否可重试,而非仅凭err!=nil;UDP读写须同时检查n和err,防止越界或忽略部分成功。

Go 网络连接建立时 net.Dial 返回的错误怎么判断是否可重试
Go 的 net.Dial(包括 net.DialTCP、net.DialUDP)失败时返回的 error 类型不统一,不能只靠 err != nil 就决定放弃。关键要看底层错误是否属于临时性(temporary)或超时(timeout)。
典型不可重试错误:DNS 解析失败(*net.DNSError 且 IsNotFound == true)、目标地址非法、本地端口被占用;典型可重试错误:连接拒绝(connect: connection refused)、网络不可达(no route to host)、临时资源不足(too many open files)。
- 用
err.(net.Error).Temporary()判断是否临时错误(适用于大多数底层系统调用错误) - 用
err.(net.Error).Timeout()判断是否超时(如context.DeadlineExceeded或底层 connect timeout) - 对
*net.OpError,需递归检查Err字段,因为包装层可能隐藏真实原因 - 避免直接字符串匹配错误信息(如
"connection refused"),不同系统返回文本可能有差异
conn, err := net.Dial("tcp", "10.0.0.99:8080", nil)
if err != nil {
if nerr, ok := err.(net.Error); ok && (nerr.Temporary() || nerr.Timeout()) {
// 可考虑重试
log.Printf("temp error, retrying: %v", err)
} else {
// 不建议重试:DNS 失败、地址格式错、权限不足等
log.Printf("fatal dial error: %v", err)
}
return
}UDP 读写时 ReadFrom 和 WriteTo 的常见错误处理陷阱
UDP 是无连接协议,ReadFrom 和 WriteTo 不会因对端宕机而报错,但可能返回非零 n + 非 nil err —— 这是 Go 的设计约定(“部分成功”),必须显式检查。
常见误操作:忽略 err、把 n == 0 当作无数据、未校验 n 导致越界 panic。
立即学习“go语言免费学习笔记(深入)”;
-
ReadFrom在 ICMP 目标不可达等情况下可能返回n > 0且err != nil(例如read: icmp response),此时 buf 中是原始 IP 包,需按协议解析 -
WriteTo对 unreachable 目标通常不报错(UDP 本身不保证送达),但若本地路由表缺失、接口 down 或防火墙丢包,可能返回err = syscall.ENETUNREACH或syscall.EHOSTUNREACH - 务必检查
n是否超出缓冲区长度,尤其在复用[]byte时
buf := make([]byte, 1500)
n, addr, err := conn.ReadFrom(buf)
if err != nil {
// 注意:即使 err != nil,n 也可能 > 0
if n > 0 && n <= len(buf) {
handlePartialUDP(buf[:n], addr)
}
log.Printf("UDP read error: %v", err)
return
}
if n == 0 {
// 空包合法(比如某些探测包),不等于错误
return
}
handleFullUDP(buf[:n], addr)监听 socket 被意外关闭后,Accept 返回 use of closed network connection 怎么安全退出
net.Listener.Accept 在 listener 被 Close() 后会立即返回 err = &net.OpError{Err: errors.New("use of closed network connection")},这是正常流程信号,不是异常。
很多代码误把它当作崩溃错误打日志甚至 panic,导致服务无法优雅停止。关键是区分「主动关闭」和「意外中断」。
- 监听器关闭前应先调用
l.Close(),之后Accept必然返回该错误,此时应跳出 accept 循环 - 不要用
errors.Is(err, net.ErrClosed)—— 它不匹配这个具体错误;正确方式是检查strings.Contains(err.Error(), "use of closed network connection")或类型断言opErr, ok := err.(*net.OpError); ok && opErr.Err != nil && opErr.Err.Error() == "use of closed network connection" - 配合
context.Context更可靠:在Accept前 select 等待 ctx.Done(),避免阻塞
for {
conn, err := l.Accept()
if err != nil {
if strings.Contains(err.Error(), "use of closed network connection") {
log.Println("listener closed, stopping accept loop")
return
}
log.Printf("accept error: %v", err)
continue
}
go handleConn(conn)
}TCP 连接中对方静默断连,Read 不报错但一直阻塞怎么办
TCP 连接空闲时对端断电、拔线、NAT 超时,本端 Read 可能永远阻塞(默认无超时)。这不是 bug,而是 TCP 协议特性:没有心跳机制,仅靠 FIN/RESET 报文通知断连,而这些报文在网络异常时可能丢失。
必须主动设置读写 deadline,否则无法感知“死连接”。注意 SetDeadline 是绝对时间,SetReadDeadline/SetWriteDeadline 才适合长连接保活。
- 每次
Read前调用conn.SetReadDeadline(time.Now().Add(30 * time.Second)),超时后Read返回err = &net.OpError{Timeout: true} - 避免在循环外一次性设置 deadline —— 它不会自动刷新,过期后后续
Read直接返回 timeout 错误 - 对需要双向保活的场景,启用
SetKeepAlive(true)并调SetKeepAlivePeriod(Linux 默认 2h,太长) - 应用层心跳(如发送 PING/PONG)比 TCP keepalive 更可控,但需双方协议支持
真正棘手的是:错误分类边界模糊。比如 read: connection reset by peer 是对方 RST,应立即关闭连接;而 read: i/o timeout 可能只是临时抖动,重试前得先确认业务语义是否允许。这类判断没法交给通用库,得结合协议状态机做。









