深入理解 Go 网络编程中的常见错误之一——connection reset by peer
使用 Go 编写网络应用程序时,大家应该或多或少都遇到过“connection reset by peer”这个错误吧。本文将探讨这一错误的含义、给出能复现的代码片段、并列举一些基本的应对方法。
"connection reset by peer" 错误通常发生在(通信双方中的一方)尝试从已重置(另一方用 RST 数据包强制关闭)的连接中读取数据时。可以通过如下代码来重现这种错误。
// ...
var done = make(chan struct{})
func server() {
listener, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
defer listener.Close()
done <- struct{}{}
conn, err := listener.Accept()
if err != nil {
log.Fatal("Server Accept", err)
os.Exit(1)
}
log.Println("Server: Accepted one connection")
data := make([]byte, 1) // ╮
if _, err := conn.Read(data); err != nil { // |
log.Fatal("Server Read", err) // |
} // ├ ①
// |
log.Printf("Server: Read %v, Closing\n", data) // |
conn.Close() // ╯
}
func client() {
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
log.Fatal("Client Dial", err)
}
// write to make the connection reset by the server side
log.Println("Client:", time.Now(), "writes 2 bytes 'ab' to server")
if _, err := conn.Write([]byte("ab")); err != nil { // ②
log.Printf("Client: %v", err)
}
time.Sleep(5 * time.Second)
data := make([]byte, 1)
if _, err := conn.Read(data); err != nil { // ③
log.Println("Client:", time.Now(), err)
}
}
func main() {
go server()
<-done
client()
}
执行结果如下,
$ go run conn_reset_by_peer.go
Server: Accepted one connection
Client: 2024-07-29 15:13:06.042689 +0800 CST m=+0.005616146 writes 2 bytes 'ab' to server
Server: Read [97], Closing
Client: 2024-07-29 15:13:11.048912 +0800 CST m=+5.011837375 read tcp [::1]:58522->[::1]:8080: read: connection reset by peer
在上面的代码中,server()
函数模拟的是一个在读取了 1 个字节之后,就立刻关闭连接的服务器①。而client()
函数模拟的客户端与该服务器建立连接后,会先发送 2 个字节“ab”②,然后等待 5 秒后尝试从服务器读取 1 个字节的数据③。
结合 tcpdump
抓包的结果来看,
此时服务器向客户端发送的不是 FIN 数据包,而是通过 RST 数据包(第 6 行)强制关闭了连接。这是因为虽然客户端发送了 2 个字节 “ab”,但服务器在关闭连接前只读取了其中的 1 个字节,这就使得套接字接收缓冲区中还有待读取的字节。也就是说,调用 conn.Close()
(底层是 close(socket_fd)
)后,是向对端发送 FIN 数据包,还是 RST 数据包,取决于套接字接收缓冲区是否为空。而当客户端尝试从这样一个被强制关闭(重置)的连接中读取数据时,就会遇到“connection reset by peer”的错误。
而如果做出如下修改,
log.Println("Server: Accepted one connection")
+ data := make([]byte, 1)
if _, err := conn.Read(data); err != nil {
********
log.Println("Server: Accepted one connection")
- data := make([]byte, 2)
if _, err := conn.Read(data); err != nil {
也就是服务器在关闭连接前,把客户端发送来的 2 个字节都读取了。那么此时客户端在 5 秒后,再尝试从一个正常关闭(通过四次挥手)的连接中读取数据时,就不会遇到“connection reset by peer”的错误,只是会读到“EOF”:
Server: Accepted one connection
Client: 2024-07-29 15:52:10.775321 +0800 CST m=+0.805954592 writes 2 bytes 'ab' to server
Server: Read [97 98], Closing
Client: 2024-07-29 15:52:15.777243 +0800 CST m=+5.807881236 EOF
对照着 tcpdump
抓包的结果来看,
这一次,服务器不再向客户端发送 RST 数据包强制关闭连接,而是通过与客户端的四次挥手(第 7 ~10 行)正常关闭了连接。
那么,该如何处理 "connection reset by peer"错误呢,我们可以采取以下几种策略:
- 检查连接状态:在发送或接收数据之前,始终检查连接的状态,确保连接仍然处于打开状态(Test on Borrow)
- 优雅地关闭连接:在不再需要连接时,始终使用适当的方法关闭连接,以避免出现意外的连接关闭
- 错误处理和重试机制:在发生连接错误时,实现适当的错误处理机制,并考虑重试连接的策略,以确保连接的可靠性
"connection reset by peer"错误是网络编程中的常见问题,通常可以通过适当的错误处理和连接管理来避免。理解这一错误的原因和处理方法对于构建稳健的网络应用程序至关重要。
转载自:https://juejin.cn/post/7396930048782860315