在Golang的网络编程实践中,网络断开是不可避免会出现的异常场景,准确识别这类错误对于程序的容错和重连逻辑至关重要。不同网络操作触发的断开错误表现不同,需要结合net包的错误定义和错误特征来判断。

Golang net包常见错误类型
Golang的net包定义了一系列和网络操作相关的错误,这些错误大多实现了net.Error接口,该接口的定义如下:
type Error interface {
error
Timeout() bool // 是否是超时错误
Temporary() bool // 是否是临时错误
}
常见的网络错误除了实现net.Error接口外,还可能属于以下类型:
- 系统调用错误:来自操作系统底层的错误,比如连接重置、连接拒绝等,在Go中通常以
*os.SyscallError类型出现 - 连接关闭错误:连接已经被主动关闭后继续操作触发的错误
- 超时错误:网络读写超过设定的超时时间触发的错误
网络断开错误的典型特征
网络断开通常分为两种场景,不同场景的错误特征有明显区别:
1. 对端主动断开连接
当对端进程崩溃、主动关闭连接或者网络链路中断时,本端进行读写操作会收到特定的错误。比如TCP连接中,对端发送FIN包或者RST包,本端读操作会返回io.EOF错误,写操作可能返回连接重置错误。
2. 网络链路中断
如果是本地网络断开、路由器故障等导致链路不通,本端的网络操作会触发超时错误或者系统调用错误,比如operation timed out或者no route to host等。
识别网络断开错误的方法
可以通过错误类型判断、错误信息匹配、net.Error接口方法调用三种方式结合识别网络断开错误。
方法一:判断错误类型
对端正常关闭连接时,读操作会返回io.EOF,这个错误可以直接识别:
import (
"io"
"net"
)
func readConn(conn net.Conn) {
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err == io.EOF {
// 对端正常关闭连接,属于网络断开场景
println("连接被对端关闭")
return
}
// 其他错误处理
}
方法二:匹配系统调用错误信息
对端异常断开或者链路中断时,可能会返回系统调用错误,比如连接重置的错误信息包含connection reset by peer,可以匹配错误字符串判断:
import (
"errors"
"net"
"strings"
)
func isConnReset(err error) bool {
// 先判断是否是系统调用错误
var syscallErr *net.OpError
if errors.As(err, &syscallErr) {
errMsg := syscallErr.Error()
// 匹配连接重置的错误特征
if strings.Contains(errMsg, "connection reset by peer") {
return true
}
// 匹配连接被拒绝的错误特征(对端端口未监听)
if strings.Contains(errMsg, "connection refused") {
return true
}
}
return false
}
方法三:结合net.Error接口判断
部分网络断开错误会实现net.Error接口,可以通过Timeout()和Temporary()方法辅助判断,不过超时错误不一定是网络断开,需要结合场景区分:
import (
"net"
)
func checkNetError(err error) string {
var netErr net.Error
if errors.As(err, &netErr) {
if netErr.Timeout() {
return "网络操作超时,可能是链路中断"
}
if netErr.Temporary() {
return "临时网络错误,可重试"
}
}
return "其他错误"
}
完整的识别实践示例
下面是一个TCP客户端读取数据的完整示例,整合了上述几种识别方法:
package main
import (
"errors"
"io"
"net"
"strings"
"time"
)
func handleConn(conn net.Conn) {
defer conn.Close()
// 设置读超时
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
if err != nil {
// 优先判断是否是EOF
if err == io.EOF {
println("对端正常关闭连接,网络断开")
return
}
// 判断是否是超时错误
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
println("读操作超时,可能网络链路中断")
return
}
// 判断是否是连接重置等系统错误
if isConnReset(err) {
println("连接被重置,网络断开")
return
}
println("其他错误:", err.Error())
return
}
// 处理正常读取的数据
println("收到数据:", string(buf[:n]))
}
}
func isConnReset(err error) bool {
var opErr *net.OpError
if errors.As(err, &opErr) {
errMsg := opErr.Error()
if strings.Contains(errMsg, "connection reset by peer") || strings.Contains(errMsg, "connection refused") {
return true
}
}
return false
}
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8080")
if err != nil {
println("连接失败:", err.Error())
return
}
handleConn(conn)
}
注意事项
需要注意错误判断的顺序,优先判断io.EOF,再判断超时错误,最后匹配系统错误信息,避免误判。另外不同操作系统的错误信息可能存在细微差异,匹配错误信息时要考虑兼容性。如果是长连接场景,可以结合心跳机制进一步确认网络是否可用,避免单纯依赖错误判断出现偏差。