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

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

您通常要忽略SIGPIPE并直接在您的代码中处理错误。 这是因为C中的信号处理程序对它们可以执行的操作有很多限制。

最简单的方法是将SIGPIPE处理程序设置为SIG_IGN 。 这将防止任何套接字或pipe道写入引起SIGPIPE信号。

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

 signal(SIGPIPE, SIG_IGN); 

如果使用send()调用,则另一个选项是使用MSG_NOSIGNAL选项,该选项将closures每个调用的SIGPIPE行为。 请注意,并非所有操作系统都支持MSG_NOSIGNAL标志。

最后,您可能还想考虑在某些操作系统上可以使用setsockopt()设置的SO_SIGNOPIPE套接字标志。 这将防止SIGPIPE被写入到它所设置的套接字中。

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

在大多数系统上(假设你正在使用C / C ++),你可以这样做:

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

实际上,不是生成SIGPIPE信号,而是返回EPIPE。

我对这个晚会太晚了,但是SO_NOSIGPIPE不是可移植的,可能不适合你的系统(这似乎是一个BSD的东西)。

一个不错的select,如果你在一个没有SO_NOSIGPIPE的Linux系统上, SO_NOSIGPIPE就是在你的send(2)调用中设置MSG_NOSIGNAL标志。

send(...,MSG_NOSIGNAL)替代write(...)send(...,MSG_NOSIGNAL) (见nobar的评论)

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

在这篇文章中,我描述了Solaris可能的解决scheme,即SO_NOSIGPIPE和MSG_NOSIGNAL都不可用。

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

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

本地处理SIGPIPE

通常情况下,最好在本地处理错误,而不是在全局信号事件处理程序中处理,因为在本地情况下,您将掌握更多的上下文以及采取何种处理方式。

我在我的一个应用程序中有一个通信层,允许我的应用程序与外部附件进行通信。 当发生写入错误时,我会在通信层中抛出exception,并让它冒泡到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信号被提出,但是当你尝试使用套接字的时候你会得到一个读/写错误,所以你需要检查这个。

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

还是应该用一个处理程序来捕获SIGPIPE并忽略它?

我相信这是对的。 你想知道什么时候另一端closures了它们的描述符,这就是SIGPIPE告诉你的。

山姆

Linux手册说:

EPIPE本地端已经在面向连接的套接字上closures了。 在这种情况下,除非设置了MSG_NOSIGNAL,否则该进程还将收到SIGPIPE。

但是对于Ubuntu 12.04,这是不对的。 我为这种情况写了一个testing,我总是收到EPIPE SIGPIPE。 如果我第二次尝试写入同一个破坏的套接字,则SIGPIPE会得到生成。 所以你不需要忽略SIGPIPE,如果这个信号发生,这意味着你的程序中的逻辑错误。

最好的做法是防止这里的崩溃?

要么根据每个人禁用sigpipes,要么忽略错误。

有没有办法检查线路的另一边是否仍在读取?

是的,使用select()。

select()在这里似乎不起作用,因为它总是说套接字是可写的。

您需要select读取位。 你可以忽略写入位。

当远端closures文件句柄时,select会告诉你有数据准备好读取。 当你去读这个,你会得到0字节,这是操作系统告诉你,文件句柄已经closures。

唯一不能忽略写入位的是,如果你正在发送大量数据,并且存在另一端可能会积压的风险,这会导致你的缓冲区被填满。 如果发生这种情况,那么试图写入文件句柄会导致程序/线程阻塞或失败。 在写作之前进行testingselect可以保护您免受这种情况的侵害,但是不能保证另一端是健康的或您的数据将会到达。

请注意,您可以从close()中获取sigpipe,也可以在写入时获取sigpipe。

closures刷新任何缓冲的数据。 如果另一端已经closures,则closures将失败,并且您将收到一个sigpipe。

如果你正在使用缓冲的TCPIP,那么写入成功只是意味着你的数据已经排队发送,并不意味着它已经发送。 在您成功致电closures之前,您不知道您的数据已发送。

Sigpipe告诉你出了什么问题,它不会告诉你什么,或者你应该怎么做。

在现代的POSIX系统下(例如Linux),可以使用sigprocmask()函数。

 #include <signal.h> void block_signal(int signal_to_block /* ie SIGPIPE */ ) { sigset_t set; sigemptyset(&set); sigaddset(&set, signal_to_block); // block the signal // sigset_t old_state; sigprocmask(SIG_BLOCK, &set, &old_state); // ... deal with old_state if required ... } 

如果您想稍后恢复以前的状态,请确保将old_state保存在安全的地方。 如果你不关心旧状态,你可能只是传递NULL

欲了解更多信息阅读手册页 。