如何防止SIGPIPEs(或妥善处理)
-
01-07-2019 - |
题
我有一个小型服务器计划,接受连接在一TCP或地方UNIX socket,读一个简单的命令并根据该命令,发送的答复。问题是,客户中可能没有兴趣在回答有时候和出口初,所以编写这座将导致SIGPIPE,并让我的服务器崩溃。什么是最佳做法,以防止的崩溃,在这里?有没有办法检查,如果该另一边的线路仍然是读书?(select()似乎并没有在这里工作,因为它总是说插座可写的).或者我是不是应该抓住SIGPIPE处理程序,并且忽视它吗?
解决方案
您通常希望忽略 SIGPIPE
并直接在代码中处理错误。这是因为C中的信号处理程序对它们可以做的事情有很多限制。
最便携的方法是将 SIGPIPE
处理程序设置为 SIG_IGN
。这将阻止任何套接字或管道写入引起 SIGPIPE
信号。
要忽略 SIGPIPE
信号,请使用以下代码:
signal(SIGPIPE, SIG_IGN);
如果您正在使用 send()
调用,另一个选项是使用 MSG_NOSIGNAL
选项,这将打开 SIGPIPE
行为每次通话时关闭。请注意,并非所有操作系统都支持 MSG_NOSIGNAL
标志。
最后,您可能还需要考虑可以在某些操作系统上使用 setsockopt()
设置的 SO_SIGNOPIPE
套接字标志。这将阻止 SIGPIPE
仅由其设置的套接字写入引起。
其他提示
另一种方法是改变插座所以它从未产生SIGPIPE上写().这更便利的图书馆,在那里,你可能不想要一个全球信号处理程序SIGPIPE。
在大多数BSD基(mac os,FreeBSD...)系统,(假设你是用C++),你可以做到这一点:
int set = 1;
setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
这在效果上,而不是SIGPIPE信号正在产生,EPIPE将返回。
我参加派对的时间太晚,但 SO_NOSIGPIPE
不可移植,可能无法在你的系统上运行(它似乎是一个BSD的东西)。
如果您使用的是没有 SO_NOSIGPIPE
的Linux系统,那么一个不错的选择是在send(2)调用上设置 MSG_NOSIGNAL
标志。
通过 send(...,MSG_NOSIGNAL)
替换 write(...)
的示例(参见 nobar 的评论)
char buf[888];
//write( sockfd, buf, sizeof(buf) );
send( sockfd, buf, sizeof(buf), MSG_NOSIGNAL );
在此 后 我描述了可能的解决方案Solaris情况时,既没有SO_NOSIGPIPE也不MSG_NOSIGNAL是可用的。
相反,我们必须暂时抑制SIGPIPE在前线执行图书馆的代码。这里是如何做到这一点:来抑制SIGPIPE我们第一次检查,如果它未决。如果是这样,这意味着,这是阻止在这个线程,我们必须做什么。如果库产生的额外SIGPIPE,它将合并有待决的一个,这是一个没有任择议定书》。如果SIGPIPE不是挂起然后我们阻止它在这个线程,并检查它是否已经堵塞。然后我们可以自由地执行我们写道。当我们恢复SIGPIPE到原来的状态,我们做到以下几点:如果SIGPIPE尚待最初,我们什么都不做。否则,我们检查,如果它正在等待现在。如果它(这意味着外出的行动已产生一个或多个SIGPIPEs),那么我们等待它在这个线程,从而清其未决状态(要做到这一点,我们使用sigtimedwait()零超时;这是为了避免阻止在一个场景在恶意用户发送SIGPIPE动的整个过程:在这种情况下,我们将看到它的未决,但是其他线可能处理它面前我们有一个改变等)。在清除未决状态,我们不阻止SIGPIPE在这个线程,但只有如果不是阻塞的原本。
例代码 https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156
本地处理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信号,但在尝试使用套接字时会出现读/写错误,因此您需要检查该信号。
您无法阻止管道远端的进程退出,如果在您完成写入之前退出,您将收到SIGPIPE信号。如果你SIG_IGN信号,那么你的写入将返回错误 - 你需要注意并对该错误作出反应。只是捕获并忽略处理程序中的信号不是一个好主意 - 您必须注意管道现在已经不存在并修改程序的行为,因此它不会再次写入管道(因为信号将再次生成,并被忽略再次,你会再试一次,整个过程可能会持续长时间并浪费大量CPU能力。)
或者我应该只使用处理程序捕获SIGPIPE并忽略它?
我相信这是正确的。你想知道另一端何时关闭了他们的描述符,这就是SIGPIPE告诉你的。
萨姆
EPIPE本地端已关闭面向连接 插座。在这种情况下,该过程也将收到SIGPIPE 除非设置了MSG_NOSIGNAL。
但对于Ubuntu 12.04来说,这是不对的。我为那个案子写了一个测试,我总是收到EPIPE withot SIGPIPE。如果我第二次尝试写入相同的断开套接字,则会生成SIGPIPE。因此,如果发生此信号,则无需忽略SIGPIPE,这意味着程序中存在逻辑错误。
防止坠机的最佳做法是什么?
根据每个人禁用sigpipes,或者捕获并忽略错误。
有没有办法检查线的另一边是否还在读?
是的,请使用select()。
select()似乎在这里不起作用,因为它总是说套接字是可写的。
您需要选择读取位。您可以忽略写位。
当远端关闭其文件句柄时,选择将告诉您有可供读取的数据。当你去阅读它时,你会得到0字节,这就是操作系统告诉你文件句柄已经关闭的方式。
唯一一次你不能忽略写位是你发送大量的数据,并且存在另一端积压的风险,这可能导致你的缓冲区被填满。如果发生这种情况,那么尝试写入文件句柄会导致程序/线程阻塞或失败。在写入之前进行测试选择将保护您免受这种影响,但它不能保证另一端健康或您的数据将会到达。
请注意,您可以从close()以及写入时获取sigpipe。
关闭刷新所有缓冲的数据。如果另一端已经关闭,则关闭将失败,您将收到一根sigpipe。
如果您使用缓冲的TCPIP,那么成功写入只意味着您的数据已排队发送,这并不意味着它已被发送。在您成功拨打电话之前,您不知道您的数据已被发送。
Sigpipe告诉你出了什么问题,它没有告诉你什么,或者你应该怎么做。
在现代POSIX系统(即Linux)下,您可以使用 sigprocmask()
函数。
#include <signal.h>
void block_signal(int signal_to_block /* i.e. SIGPIPE */ )
{
sigset_t set;
sigset_t old_state;
// get the current state
//
sigprocmask(SIG_BLOCK, NULL, &old_state);
// add signal_to_block to that existing state
//
set = old_state;
sigaddset(&set, signal_to_block);
// block that signal also
//
sigprocmask(SIG_BLOCK, &set, NULL);
// ... deal with old_state if required ...
}
如果您想稍后恢复以前的状态,请确保将 old_state
保存在安全的地方。如果多次调用该函数,则需要使用堆栈或仅保存第一个或最后一个 old_state
...或者可能具有删除特定阻塞信号的函数。
欲了解更多信息,请阅读手册页。