我有一个小的服务器程序,它接受TCP或本地UNIX套接字上的连接,读取一个简单的命令并(取决于命令)发送一个应答。

问题是客户可能对答案不感兴趣,有时会提前退出。因此写入该套接字将导致SIGPIPE并使我的服务器崩溃。

在这里防止崩溃的最佳实践是什么?有没有办法检查另一边是否还在阅读?(select()似乎在这里不起作用,因为它总是说套接字是可写的)。或者我应该用处理程序捕获SIGPIPE并忽略它?


当前回答

另一种方法是改变套接字,这样它就不会在write()时生成SIGPIPE。这在库中更方便,因为在库中您可能不需要SIGPIPE的全局信号处理程序。

在大多数基于bsd (MacOS, FreeBSD…)的系统上,(假设你使用的是C/ c++),你可以通过以下方法做到这一点:

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

这样,就不会生成SIGPIPE信号,而是返回EPIPE信号。

其他回答

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

最可移植的方法是将SIGPIPE处理程序设置为SIG_IGN。这将防止任何套接字或管道写入导致SIGPIPE信号。

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

signal(SIGPIPE, SIG_IGN);

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

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

我加入这个聚会非常晚,但是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 );

在现代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…或者有一个函数可以移除特定的阻塞信号。

更多信息请阅读手册页。

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

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

Sam

Linux手册说:

EPIPE面向连接的本端已经关闭 套接字。在这种情况下,进程还将接收一个SIGPIPE 除非设置了MSG_NOSIGNAL。

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