

新闻资讯
技术学院Go中net.Dial失败时,需通过err.(net.Error).Temporary()或Timeout()判断是否可重试,而非仅凭err!=nil;UDP读写须同时检查n和err,防止越界或忽略部分成功。
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
}ReadFrom 和 WriteTo 的常见错误处理陷阱UDP 是无连接协议,ReadFrom 和 WriteTo 不会因对端宕机而报错,但可能返回非零 n + 非 nil err —— 这是 Go 的设计约定(“部分成功”),必须显式检查。
常见误操作:忽略 err、把 n == 0 当作无数据、未校验 n 导致越界 panic。
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)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)
}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}
Read 直接返回 timeout 错误SetKeepAlive(true) 并调 SetKeepAlivePeriod(Linux 默认 2h,太长)真正棘手的是:错误分类边界模糊。比如 read: connection reset by peer 是对方 RST,应立即关闭连接;而 read: i/o timeout 可能只是临时抖动,重试前得先确认业务语义是否允许。这类判断没法交给通用库,得结合协议状态机做。