如何防止SIGPIPE(或者正确处理)
我有一个小型服务器程序,该程序接受TCP或者本地UNIX套接字上的连接,读取一个简单命令,并根据命令发送回复。问题是客户端有时可能对答案没有兴趣,并且提早退出,因此写入该套接字将导致SIGPIPE并使我的服务器崩溃。防止此处崩溃的最佳实践是什么?有没有办法检查行的另一端是否仍在读取? (select()似乎在这里不起作用,因为它总是说套接字是可写的)。还是应该仅使用处理程序来捕获SIGPIPE并忽略它?
解决方案
Or should I just catch the SIGPIPE with a handler and ignore it?
我相信那是对的。我们想知道另一端何时关闭其描述符,而这正是SIGPIPE告诉。
山姆
我们通常希望忽略SIGPIPE并直接在代码中处理错误。这是因为C语言中的信号处理程序对其功能有很多限制。
最简便的方法是将SIGPIPE处理程序设置为SIG_IGN。这将防止任何套接字或者管道写操作引起" SIGPIPE"信号。
要忽略SIGPIPE信号,请使用以下代码:
signal(SIGPIPE, SIG_IGN);
如果我们使用send()
调用,则另一个选择是使用MSG_NOSIGNAL
选项,这将在每次调用的基础上关闭SIGPIPE行为。请注意,并非所有操作系统都支持
MSG_NOSIGNAL`标志。
最后,我们可能还想考虑在某些操作系统上可以通过setsockopt()设置的SO_SIGNOPIPE套接字标志。这将防止SIGPIPE仅仅由对设置在其上的套接字的写入引起。
我们不能阻止管道远端的进程退出,如果在完成写入之前退出该进程,则会收到SIGPIPE信号。如果我们对信号SIG_IGN进行了写操作,那么写操作将返回一个错误,我们需要注意并对该错误做出反应。仅捕获并忽略处理程序中的信号不是一个好主意-我们必须注意,管道现已关闭并修改了程序的行为,因此它不会再次写入管道(因为信号将再次生成,并再次被忽略,我们将再试一次,整个过程可能会持续很长时间,并浪费大量CPU能力)。
另一种方法是更改套接字,以使其永远不会在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的事情)。
例如,如果我们使用的是不带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 );
在本文中,我描述了当SO_NOSIGPIPE或者MSG_NOSIGNAL都不可用时针对Solaris情况的可能解决方案。
Instead, we have to temporarily suppress SIGPIPE in the current thread that executes library code. Here's how to do this: to suppress SIGPIPE we first check if it is pending. If it does, this means that it is blocked in this thread, and we have to do nothing. If the library generates additional SIGPIPE, it will be merged with the pending one, and that's a no-op. If SIGPIPE is not pending then we block it in this thread, and also check whether it was already blocked. Then we are free to execute our writes. When we are to restore SIGPIPE to its original state, we do the following: if SIGPIPE was pending originally, we do nothing. Otherwise we check if it is pending now. If it does (which means that out actions have generated one or more SIGPIPEs), then we wait for it in this thread, thus clearing its pending status (to do this we use sigtimedwait() with zero timeout; this is to avoid blocking in a scenario where malicious user sent SIGPIPE manually to a whole process: in this case we will see it pending, but other thread may handle it before we had a change to wait for it). After clearing pending status we unblock SIGPIPE in this thread, but only if it wasn't blocked originally.
https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156上的示例代码