broken pipe 是一个TCP/IP错误,发生在你向另一端(对等体)已经关闭了底层连接的流中写入时。第一次写到已关闭的连接时,对等体会回复一个 RST数据包,表示应该立即终止连接。第二次写到已经接收到数据包的套接字时,会导致 RST导致broken pipe 错误。要检测Go中的broken pipe ,检查对等体返回的错误是否等于 syscall.EPIPE.通常情况下,当服务器在客户端向其发送数据时崩溃,就可以看到这个错误。

重现 broken pipe 的错误

在下面的例子中,我们通过创建一个服务器和客户端来重现broken pipe 的错误:

  • 服务器读取一个字节,然后关闭连接
  • 客户端发送三个字节,两个字节之间的间隔为一秒

服务器收到客户端的第一个字节并关闭连接。客户端向已关闭的连接发送的下一个字节会导致服务器回复一个 RST数据包。收到该数据包的套接字 RST当更多的字节被发送到它时,将返回broken pipe 错误。这就是客户端向服务器发送最后一个字节时的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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)
}
// write to make the connection closed on the server side
if _, err := conn.Write([]byte("a")); err != nil {
log.Printf("client: %v", err)
}
time.Sleep(1 * time.Second)
// write to generate an RST packet
if _, err := conn.Write([]byte("b")); err != nil {
log.Printf("client: %v", err)
}
time.Sleep(1 * time.Second)
// write to generate the broken pipe error
if _, err := conn.Write([]byte("c")); err != nil {
log.Printf("client: %v", err)
if errors.Is(err, syscall.EPIPE) {
log.Print("This is broken pipe error")
}
}
}
func main() {
go server()
time.Sleep(3 * time.Second) // wait for server to run

client()
}

输出

1
2
2021/10/21 19:10:01 client: write tcp 127.0.0.1:50389->127.0.0.1:8080: write: broken pipe
2021/10/21 19:10:01 This is broken pipe error

处理 broken pipe 错误

为了处理broken pipe ,你需要检查从连接的另一端返回的错误是否是一个 syscall.EPIPE.在上面的例子中,我们使用errors.Is() 函数进行这种检查,如果出现这种情况,就打印消息"This is broken pipe error"broken pipe 可以在客户端或服务器端看到,这取决于哪一方试图向已关闭的连接写入。通常情况下,没有必要以任何特殊的方式处理它,因为连接可能被通信的任何一方中断是正常的。例如,你可以忽略这个错误,记录它或者在它发生时重新连接。

broken pipe 和的区别connection reset by peer

broken pipe 通常情况下,当你在连接中写入RST发送后,你会得到,而当你从连接中读出RST后,你会得到connection reset by peer 错误。请查看我们的文章《对等错误重置连接》,以更好地理解这两个错误之间的区别。