我有一个小型服务器计划,接受连接在一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告诉你的。

萨姆

Linux手册说:

  

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 ...或者可能具有删除特定阻塞信号的函数。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top