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

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

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


当前回答

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

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

Sam

其他回答

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

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

Sam

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

在这里防止崩溃的最佳实践是什么?

要么像每个人一样禁用sigpipe,要么捕获并忽略错误。

有没有办法检查另一边是否还在阅读?

是的,使用select()。

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

您需要在读取位上进行选择。你可以忽略写的部分。

当远端关闭其文件句柄时,select将告诉您有数据可以读取。当你读取它时,你会得到0字节,这是操作系统告诉你文件句柄已经关闭的方式。

唯一不能忽略写位的情况是,如果您正在发送大量数据,那么另一端有积压的风险,这可能会导致缓冲区被填满。如果发生这种情况,那么尝试写入文件句柄可能会导致程序/线程阻塞或失败。在写入之前测试select可以避免这种情况,但它不能保证另一端是健康的,也不能保证数据会到达。

请注意,您可以从close()以及写入时获得sigpipe。

Close刷新所有缓冲的数据。如果另一端已经关闭,那么关闭将失败,您将收到一个sigpipe。

如果您正在使用缓冲的TCPIP,那么成功的写入仅仅意味着您的数据已经排队发送,而不意味着它已经发送。直到成功调用close,您才知道数据已经发送。

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

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

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