什么导致了“Broken Pipe错误”?

106

我知道当对端套接字关闭时会引发"broken pipe"错误。

但是,在我的测试中,我注意到即使在对端关闭后立即在此端进行'send'调用,也不总是导致"broken pipe"错误。

例如:

在关闭对端套接字(我尝试通过调用close进行清洁关闭,也尝试过通过杀死对等方进行异常关闭)后,如果我尝试发送40个字节,则不会出现"broken pipe"错误,但是,如果我尝试发送40000个字节,则会立即出现 "broken pipe" 错误。

到底是什么原因导致了"broken pipe"错误,它的行为是否可以预测?

6个回答

75

网络关闭需要一定时间才能被观察到,通常在关闭后约2分钟(是的,分钟!)才会假定所有发送到该端口的分组均已失效。某个时刻会检测到错误条件。使用小写操作,您将在系统的MTU内,因此消息将被排队等待发送。使用大写操作,字节数超过了MTU,系统会更快地发现问题。如果忽略SIGPIPE信号,则函数将在检测到连接中断时返回EPIPE错误。


4
我认为排队传输和定期发送不是一个解决方法。如果您的应用程序可以容忍延迟,那么等到有超过MTU可发送的数据再进行排队传输可能是一种解决方法。 - Jonathan Leffler
我认为它与 https://en.wikipedia.org/wiki/Nagle%27s_algorithm 密切相关。 - sectus
3
MTU:https://en.wikipedia.org/wiki/Maximum_transmission_unit - TheRealFakeNews

19
当前套接字的状态是由'keep-alive'活动决定的。在您的情况下,当您发出send调用时,keep-alive活动告诉套接字处于活动状态,因此send调用将把所需的数据(40个字节)写入缓冲区并返回而不会产生任何错误。
当您发送更大的数据块时,发送调用会进入阻塞状态。
发送手册也证实了这一点:
当消息不适合套接字的发送缓冲区时,send()通常会阻塞,除非套接字已被放置在非阻塞I/O模式中。在非阻塞模式下,在这种情况下它会返回EAGAIN。
因此,当阻塞等待可用缓冲区时,如果调用者被通知(通过keep-alive机制)另一端不再存在,则发送调用将失败。
根据提供的信息准确预测情景比较困难,但我认为这应该是您的问题原因。

1
套接字的当前状态是通过ACK活动观察的。Keepalive只是一个小的源ACK活动,而且默认情况下是关闭的。 - user207421

6
也许 40 字节可以放入管道缓冲区,而 40000 字节则不行?
编辑:
当您尝试向关闭的管道写入数据时,发送进程会收到 SIGPIPE 信号。我不确定信号何时发送以及管道缓冲区对此的影响。您可以使用 sigaction 调用捕获该信号来恢复。

1

当对等方关闭时,您只是不知道它是否停止发送还是同时停止发送和接收。因为TCP允许这样做,顺便说一下,您应该知道关闭和关闭之间的区别。 如果对等方停止发送和接收,首先发送一些字节,它将成功。但是对等方内核会向您发送RST。因此,随后发送一些字节,您的内核将向您发送SIGPIPE信号,如果您捕获或忽略此信号,则在发送返回时,您将仅收到Broken pipe错误,或者如果您不这样做,则程序的默认行为是崩溃。


0

会话超时设置可能是断开管道的原因。
例如:服务器会话超时3小时,负载均衡器为1小时。
负载均衡器在1小时后阻塞,但服务器仍然继续发送响应。在这种情况下,管道的一端被破坏。

但也可能是用户行为。用户在下载过程中关闭页面等。


0
您不需要通过网络发送代码来获得此错误。例如,这是我的最喜欢的 Python 3 代码(现在):
while 1:print()

会打印很多换行符,最终会引发一个BrokenPipeError错误。因此,如果您向IO或其他流写入过多的数据,就会出现这个错误。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接