Answers:
通常,您希望忽略SIGPIPE
并直接在代码中处理错误。这是因为C语言中的信号处理程序对其功能有很多限制。
最方便的方法是将SIGPIPE
处理程序设置为SIG_IGN
。这将防止任何套接字或管道写入引起SIGPIPE
信号。
要忽略SIGPIPE
信号,请使用以下代码:
signal(SIGPIPE, SIG_IGN);
如果您正在使用send()
呼叫,则另一个选项是使用该MSG_NOSIGNAL
选项,它将SIGPIPE
基于每个呼叫关闭行为。请注意,并非所有操作系统都支持该MSG_NOSIGNAL
标志。
最后,您可能还需要考虑SO_SIGNOPIPE
可以setsockopt()
在某些操作系统上设置的套接字标志。这将防止SIGPIPE
仅由于对其设置的套接字进行写操作而引起的。
另一种方法是更改套接字,以便它永远不会在write()上生成SIGPIPE。这在库中更为方便,在库中您可能不需要SIGPIPE的全局信号处理程序。
在大多数基于BSD的(MacOS,FreeBSD ...)系统上(假设您使用的是C / C ++),可以使用以下方法执行此操作:
int set = 1;
setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
启用此功能后,将不返回SIGPIPE信号,而是返回EPIPE。
我参加聚会的时间太晚了,但是SO_NOSIGPIPE
不方便携带,可能无法在您的系统上运行(这似乎是BSD的事情)。
例如,如果您使用的是不带Linux系统的不错选择,那就是在send(2)调用上SO_NOSIGPIPE
设置MSG_NOSIGNAL
标志。
示例替换write(...)
为send(...,MSG_NOSIGNAL)
(请参阅nobar的注释)
char buf[888];
//write( sockfd, buf, sizeof(buf) );
send( sockfd, buf, sizeof(buf), MSG_NOSIGNAL );
QTcpSocket
对象包装套接字用法,我不得不用write
OS send
(使用socketDescriptor
方法)替换方法调用。有谁知道可以在QTcpSocket
课堂上设置此选项的更清洁的选项?
在这种交我描述了可能的解决方案适用于Solaris情况下,当既不SO_NOSIGPIPE也不MSG_NOSIGNAL是可用的。
相反,我们必须在执行库代码的当前线程中暂时禁止SIGPIPE。方法如下:为了抑制SIGPIPE,我们首先检查它是否未决。如果是这样,则意味着它已在该线程中被阻塞,我们无需执行任何操作。如果该库生成其他SIGPIPE,它将与未决的SIGPIPE合并,这是无操作的。如果SIGPIPE没有挂起,则我们在此线程中将其阻止,并检查它是否已被阻止。这样我们就可以自由地执行我们的写操作了。当我们要将SIGPIPE恢复到原始状态时,请执行以下操作:如果SIGPIPE最初处于挂起状态,则不执行任何操作。否则,我们检查它是否正在等待处理。如果确实如此(这意味着out操作已生成一个或多个SIGPIPE),那么我们在此线程中等待,从而清除其挂起状态(为此,我们使用sigtimedwait()并使用零超时;这是为了避免在恶意用户手动将SIGPIPE发送给整个进程的情况下进行阻塞:在这种情况下,我们将看到它处于挂起状态,但是其他线程可能在我们进行更改之前等待它)。清除挂起状态后,我们将在此线程中解除对SIGPIPE的阻止,但前提是原本没有阻止它。
https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156上的示例代码
通常最好是在本地处理错误,而不是在全局信号事件处理程序中处理错误,因为在本地,您将对正在发生的事情和采取的措施有更多的了解。
我的一个应用程序中有一个通信层,该层允许我的应用程序与外部附件进行通信。当发生写错误时,我在通信层中引发了异常,并使其冒泡到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,要么捕获并忽略该错误。
有没有办法检查行的另一端是否仍在读取?
是的,使用select()。
select()似乎在这里不起作用,因为它总是说套接字是可写的。
您需要选择读取位。您可能会忽略写位。
当远端关闭其文件句柄时,select会告诉您有准备读取的数据。当您阅读这些内容时,将获得0字节,这是操作系统告诉您文件句柄已关闭的方式。
唯一不能忽略写位的情况是,如果要发送大量数据,并且另一端存在积压的风险,这可能导致缓冲区填满。如果发生这种情况,那么尝试写入文件句柄可能会导致程序/线程阻塞或失败。在编写之前测试select可以保护您免受此伤害,但不能保证另一端运行状况良好或您的数据即将到达。
请注意,您可以从close()以及编写时获取一个sigpipe。
Close刷新所有缓冲的数据。如果另一端已经关闭,则关闭将失败,并且您将收到一个信号管道。
如果您使用的是缓冲的TCPIP,则成功写入仅表示您的数据已排队等待发送,并不意味着它已发送。在成功调用close之前,您不知道数据已发送。
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
...,或者可能具有删除特定受阻信号的函数。
有关更多信息,请阅读手册页。
sigprocmask
会添加到一组受阻信号中,因此您只需一个电话就可以完成所有这些操作。
old_state
以便以后选择时可以将其还原。如果您知道不需要恢复状态,则确实不需要以这种方式读取和存储状态。
sigprocmask()
读取旧状态,sigaddset
修改它的调用,第二个sigprocmask()
写入旧状态的调用。您可以删除第一个通话,使用进行初始化,sigemptyset(&set)
然后将第二个通话更改为sigprocmask(SIG_BLOCK, &set, &old_state)
。