处理 Go 中的 'connection reset by peer' 错误

2022-07-28
2分钟阅读时长

原文链接:https://gosamples.dev/connection-reset-by-peer/

connection reset by peer是在另一端(对等方)意外关闭连接时发生的TCP/IP错误。 当你从你的一端发送一个数据包,但另一端崩溃并强行关闭与RST 数据包的连接而不是TCP FIN 正常情况下用于关闭连接的连接时,就会发生这种情况。在 Go 中,您可以connection reset by peer通过检查对等方返回的错误是否等于 来检测syscall.ECONNRESET

重现connection reset by peer错误

我们可以通过创建执行以下操作的服务器和客户端来重现错误:

  • 服务器读取一个字节然后关闭连接
  • 客户端发送超过一个字节

如果服务器使用套接字接收缓冲区中的剩余字节关闭连接,则RST向客户端发送一个数据包。当客户端试图从这样一个关闭的连接中读取时,它会得到connection reset by peer错误。

请参阅以下示例,该示例模拟了此行为。

package main

import (
    "errors"
    "log"
    "net"
    "os"
    "syscall"
    "time"
)

func server() {
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal(err)
    }

    defer listener.Close()

    conn, err := listener.Accept()
    if err != nil {
        log.Fatal("server", err)
        os.Exit(1)
    }
    data := make([]byte, 1)
    if _, err := conn.Read(data); err != nil {
        log.Fatal("server", err)
    }

    conn.Close()
}

func client() {
    conn, err := net.Dial("tcp", "localhost:8080")
    if err != nil {
        log.Fatal("client", err)
    }

    if _, err := conn.Write([]byte("ab")); err != nil {
        log.Printf("client: %v", err)
    }

    time.Sleep(1 * time.Second) // wait for close on the server side

    data := make([]byte, 1)
    if _, err := conn.Read(data); err != nil {
        log.Printf("client: %v", err)
        if errors.Is(err, syscall.ECONNRESET) {
            log.Print("This is connection reset by peer error")
        }
    }
}

func main() {
    go server()

    time.Sleep(3 * time.Second) // wait for server to run

    client()
}

输出:

2021/10/20 19:01:58 client: read tcp [::1]:59897->[::1]:8080: read: connection reset by peer
2021/10/20 19:01:58 This is connection reset by peer error

处理connection reset by peer错误

connection reset by peer通常,您可以在响应从客户端发送到服务器的请求时看到错误。这意味着服务器发生了一些不好的事情:它已经重新启动,程序已经崩溃,或者发生了其他导致连接被强制关闭的问题。由于 TCP 连接可能会中断,因此无需connection reset by peer在客户端以任何特殊方式进行处理。您可以记录错误、忽略它或在发生错误时重试连接。在上面的示例中,我们通过errors.Is()检查 返回的错误是否是syscall.ECONNRESET .

connection reset by peer和之间的区别broken pipe

当对等方(另一端)意外关闭底层连接时,两者都会connection reset by peer发生错误。broken pipe但是,它们之间存在细微差别。通常,connection reset by peer当您在服务器发送数据包后从连接中读取时会收到错误消息,而当您在之后****写入连接时会收到错误消息。RST RST broken pipe

查看如何处理broken pipeGo post 中的错误,这里会找到另一个生成RST数据包的示例和broken pipe错误。

将上面示例中的函数替换client()为以下代码以重现broken pipe错误。

func client() {
    conn, err := net.Dial("tcp", "localhost:8080")
    if err != nil {
        log.Fatal("client", err)
    }

    if _, err := conn.Write([]byte("ab")); err != nil {
        log.Printf("client: %v", err)
    }

    time.Sleep(1 * time.Second) // wait for close on the server side

    if _, err := conn.Write([]byte("b")); err != nil {
        log.Printf("client: %v", err)
    }
}

使用新客户端,您将看到输出:

2021/10/20 19:55:40 client: write tcp [::1]:60399->[::1]:8080: write: broken pipe

请注意,这些简单的示例并未涵盖所有可能发生connection reset by peerbroken pipe情况。可以看到这些错误的情况还有很多,在什么情况下看到什么错误需要对TCP 设计有深入的了解。

关注公众号获得更多精彩文章

公众号:程序员大兵