当前位置:  开发笔记 > 编程语言 > 正文

如何防止SIGPIPE(或正确处理它们)

如何解决《如何防止SIGPIPE(或正确处理它们)》经验,为你挑选了7个好方法。

我有一个小型服务器程序,它接受TCP或本地UNIX套接字上的连接,读取一个简单的命令,并根据命令发送一个回复.问题是客户端有时可能对答案没兴趣并且提前退出,因此写入该套接字将导致SIGPIPE并使我的服务器崩溃.什么是防止崩溃的最佳做法?有没有办法检查线的另一边是否还在读?(select()似乎在这里不起作用,因为它总是说套接字是可写的).或者我应该用处理程序捕获SIGPIPE并忽略它?



1> dvorak..:

您通常希望忽略SIGPIPE并直接在代码中处理错误.这是因为C中的信号处理程序对它们可以做的事情有很多限制.

最便携的方法是将SIGPIPE处理程序设置为SIG_IGN.这将防止任何套接字或管道写入引起SIGPIPE信号.

要忽略该SIGPIPE信号,请使用以下代码:

signal(SIGPIPE, SIG_IGN);

如果您正在使用该send()呼叫,则另一个选项是使用该MSG_NOSIGNAL选项,该选项将SIGPIPE基于每个呼叫关闭该行为.请注意,并非所有操作系统都支持该MSG_NOSIGNAL标志.

最后,您可能还需要考虑SO_SIGNOPIPE可以setsockopt()在某些操作系统上设置的套接字标志.这样可以防止SIGPIPE仅对其设置的套接字进行写操作.


为了明确这一点:signal(SIGPIPE,SIG_IGN);
对于套接字,使用带有MSG_NOSIGNAL的send()非常容易,正如@talash所说.
如果您的退出返回代码为141(128 + 13:SIGPIPE),则可能需要这样做.SIG_IGN是忽略信号处理程序.
如果我的程序写入损坏的管道(在我的情况下是套接字),会发生什么?SIG_IGN使程序忽略SIG_PIPE,但是这会导致send()做一些不受欢迎的事情吗?
值得一提的是,自从我找到它一次以来,后来就忘记了它并感到困惑,然后第二次发现了它。调试器(我知道gdb确实如此)会覆盖您的信号处理,因此不会忽略忽略的信号!在调试器外部测试您的代码,以确保SIGPIPE不再出现。http://stackoverflow.com/questions/6821469/how-to-prevent-sigpipe-or-prevent-the-server-from-ending

2> 小智..:

另一种方法是更改​​套接字,以便它永远不会在write()上生成SIGPIPE.这在库中更方便,您可能不需要SIGPIPE的全局信号处理程序.

在大多数基于BSD(MacOS,FreeBSD ...)的系统上,(假设你使用的是C/C++),你可以这样做:

int set = 1;
setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));

实现此功能后,将返回EPIPE,而不是生成SIGPIPE信号.


这听起来不错,但是在Linux中似乎并不存在SO_NOSIGPIPE - 所以它不是一个广泛的便携式解决方案.
在Linux中,我在[send]的标志中看到了MSG_NOSIGNAL选项(http://linux.die.net/man/2/send)

3> sklnd..:

我在派对上已经很晚了,但是SO_NOSIGPIPE不可移植,可能无法在你的系统上运行(它似乎是一个BSD的东西).

如果你在一个Linux系统上,那么一个不错的选择就是在你的send(2)调用上SO_NOSIGPIPE设置MSG_NOSIGNAL标志.

实施例替换write(...)send(...,MSG_NOSIGNAL)(参见nobar的评论)

char buf[888];
//write( sockfd, buf, sizeof(buf) );
send(    sockfd, buf, sizeof(buf), MSG_NOSIGNAL );


换句话说,使用send(...,MSG_NOSIGNAL)作为write()的替代,你将不会得到SIGPIPE.这应该适用于套接字(在支持的平台上),但send()似乎仅限于使用套接字(而不是管道),因此这不是SIGPIPE问题的一般解决方案.

4> 小智..:

在这篇文章中,当SO_NOSIGPIPE和MSG_NOSIGNAL都不可用时,我描述了Solaris案例的可能解决方案.

相反,我们必须在执行库代码的当前线程中暂时禁止SIGPIPE.以下是如何执行此操作:要抑制SIGPIPE,我们首先检查它是否处于挂起状态.如果是这样,这意味着它在此线程中被阻止,我们不得不做任何事情.如果库生成了额外的SIGPIPE,它将与待处理的SIGPIPE合并,这是一个无操作.如果SIGPIPE没有挂起,那么我们在此线程中阻止它,并检查它是否已被阻止.然后我们可以自由地执行我们的写入.当我们要将SIGPIPE恢复到其原始状态时,我们会执行以下操作:如果SIGPIPE最初处于挂起状态,则我们不执行任何操作.否则我们现在检查它是否正在等待.如果它(这意味着out动作已生成一个或多个SIGPIPE),那么我们在此线程中等待它,从而清除其挂起状态(为此我们使用sigtimedwait(),零超时;这是为了避免阻塞恶意用户手动将SIGPIPE发送到整个进程的场景:在这种情况下,我们将看到它处于挂起状态,但是在我们进行等待更改之前,其他线程可以处理它.清除挂起状态后,我们在此线程中取消阻止SIGPIPE,但前提是它最初未被阻止.

https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156上的示例代码



5> Sam..:

在当地处理SIGPIPE

通常最好在本地处理错误,而不是在全局信号事件处理程序中处理错误,因为在本地,您将有更多关于正在发生的事情以及采取什么措施的上下文.

我的一个应用程序中有一个通信层,允许我的应用程序与外部附件进行通信.当发生写入错误时,我在通信层中抛出异常并让它冒泡到try catch块来处理它.

码:

忽略SIGPIPE信号以便您可以在本地处理它的代码是:

// We expect write failures to occur but we want to handle them where 
// the error occurs rather than in a SIGPIPE handler.
signal(SIGPIPE, SIG_IGN);

此代码将阻止引发SIGPIPE信号,但在尝试使用套接字时会出现读/写错误,因此您需要检查该信号.



6> Jonathan Lef..:

您无法阻止管道远端的进程退出,如果它在您完成写入之前退出,您将收到SIGPIPE信号.如果你SIG_IGN信号,那么你的写入将返回错误 - 你需要注意并对该错误作出反应.只是捕获并忽略处理程序中的信号不是一个好主意 - 您必须注意管道现在已经不存在并修改程序的行为,因此它不会再次写入管道(因为信号将再次生成,并被忽略再次,你会再试一次,整个过程可能会持续长时间并浪费大量CPU能力).


@Math:忽略SIGPIPE并注意写入错误.

7> Sam Reynolds..:

或者我应该用处理程序捕获SIGPIPE并忽略它?

我相信这是正确的.你想知道另一端何时关闭了他们的描述符,这就是SIGPIPE告诉你的.

山姆

推荐阅读
谢谢巷议
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有