我有一个小型服务器程序,它接受 TCP 或本地 UNIX 套接字上的连接,读取一个简单的命令并(取决于命令)发送回复。
问题是客户可能对答案不感兴趣,有时会提前退出。 因此写入该套接字将导致 SIGPIPE 并使我的服务器崩溃。
防止这里崩溃的最佳做法是什么? 有没有办法检查线路的另一端是否仍在阅读? (select()
似乎在这里不起作用,因为它总是说套接字是可写的)。 或者我应该用处理程序捕获 SIGPIPE
并忽略它?
I have a small server program that accepts connections on a TCP or local UNIX socket, reads a simple command and (depending on the command) sends a reply.
The problem is that the client may have no interest in the answer and sometimes exits early. So writing to that socket will cause a SIGPIPE
and make my server crash.
What's the best practice to prevent the crash here? Is there a way to check if the other side of the line is still reading? (select()
doesn't seem to work here as it always says the socket is writable). Or should I just catch the SIGPIPE
with a handler and ignore it?
发布评论
评论(11)
我相信这是正确的。 您想知道另一端何时关闭其描述符,这就是 SIGPIPE 告诉您的。
山姆
I believe that is right on. You want to know when the other end has closed their descriptor and that's what SIGPIPE tells you.
Sam
您无法阻止管道远端的进程退出,如果它在您完成写入之前退出,您将收到 SIGPIPE 信号。 如果您 SIG_IGN 信号,那么您的写入将返回错误 - 您需要注意该错误并对该错误做出反应。 仅捕获并忽略处理程序中的信号并不是一个好主意 - 您必须注意管道现在已失效并修改程序的行为,以便它不会再次写入管道(因为信号将再次生成并被忽略)再次,您将重试,整个过程可能会持续很长时间并浪费大量CPU资源)。
You cannot prevent the process on the far end of a pipe from exiting, and if it exits before you've finished writing, you will get a SIGPIPE signal. If you SIG_IGN the signal, then your write will return with an error - and you need to note and react to that error. Just catching and ignoring the signal in a handler is not a good idea -- you must note that the pipe is now defunct and modify the program's behaviour so it does not write to the pipe again (because the signal will be generated again, and ignored again, and you'll try again, and the whole process could go on for a long time and waste a lot of CPU power).
本地处理 SIGPIPE
通常最好在本地处理错误,而不是在全局信号事件处理程序中处理,因为在本地,您将有更多关于正在发生的情况以及采取什么措施的上下文。
我的一个应用程序中有一个通信层,允许我的应用程序与外部配件进行通信。 当发生写入错误时,我在通信层中抛出异常,并让它冒泡到 try catch 块以在那里处理它。
代码:
忽略 SIGPIPE 信号以便您可以在本地处理它的代码是:
此代码将阻止引发 SIGPIPE 信号,但在尝试使用套接字时会出现读/写错误,因此您需要检查一下。
Handle SIGPIPE Locally
It's usually best to handle the error locally rather than in a global signal event handler since locally you will have more context as to what's going on and what recourse to take.
I have a communication layer in one of my apps that allows my app to communicate with an external accessory. When a write error occurs I throw and exception in the communication layer and let it bubble up to a try catch block to handle it there.
Code:
The code to ignore a SIGPIPE signal so that you can handle it locally is:
This code will prevent the SIGPIPE signal from being raised, but you will get a read / write error when trying to use the socket, so you will need to check for that.
您通常希望忽略
SIGPIPE
并直接在代码中处理错误。 这是因为 C 中的信号处理程序对其功能有很多限制。最可移植的方法是将
SIGPIPE
处理程序设置为SIG_IGN
。 这将防止任何套接字或管道写入引起SIGPIPE
信号。要忽略
SIGPIPE
信号,请使用以下代码:如果您使用
send()
调用,另一种选择是使用MSG_NOSIGNAL
选项,这将在每次调用的基础上关闭SIGPIPE
行为。 请注意,并非所有操作系统都支持MSG_NOSIGNAL
标志。最后,您可能还需要考虑可以使用 SO_NOSIGPIPE 套接字标志某些操作系统(例如源自 BSD 的操作系统)上的 ="nofollow noreferrer">
setsockopt()
。 这将防止仅写入其所设置的套接字而导致SIGPIPE
发生。You generally want to ignore the
SIGPIPE
and handle the error directly in your code. This is because signal handlers in C have many restrictions on what they can do.The most portable way to do this is to set the
SIGPIPE
handler toSIG_IGN
. This will prevent any socket or pipe write from causing aSIGPIPE
signal.To ignore the
SIGPIPE
signal, use the following code:If you're using the
send()
call, another option is to use theMSG_NOSIGNAL
option, which will turn theSIGPIPE
behavior off on a per call basis. Note that not all operating systems support theMSG_NOSIGNAL
flag.Lastly, you may also want to consider the
SO_NOSIGPIPE
socket flag that can be set withsetsockopt()
on some operating systems (e.g. those derived from BSD). This will preventSIGPIPE
from being caused by writes just to the sockets it is set on.另一种方法是更改套接字,使其永远不会在 write() 上生成 SIGPIPE。 这在库中更方便,您可能不需要 SIGPIPE 的全局信号处理程序。
在大多数基于 BSD(MacOS、FreeBSD...)的系统上(假设您使用的是 C/C++),您可以通过以下方式执行此操作:
生效后,将返回 EPIPE,而不是生成 SIGPIPE 信号。
Another method is to change the socket so it never generates SIGPIPE on write(). This is more convenient in libraries, where you might not want a global signal handler for SIGPIPE.
On most BSD-based (MacOS, FreeBSD...) systems, (assuming you are using C/C++), you can do this with:
With this in effect, instead of the SIGPIPE signal being generated, EPIPE will be returned.
我来晚了,但是
SO_NOSIGPIPE
不可移植,并且可能无法在您的系统上运行(这似乎是 BSD 的问题)。如果您使用的是没有
SO_NOSIGPIPE
的 Linux 系统,一个不错的选择是在 send(2) 调用上设置MSG_NOSIGNAL
标志。将
write(...)
替换为send(...,MSG_NOSIGNAL)
的示例(请参阅 nobar 的评论)I'm super late to the party, but
SO_NOSIGPIPE
isn't portable, and might not work on your system (it seems to be a BSD thing).A nice alternative if you're on, say, a Linux system without
SO_NOSIGPIPE
would be to set theMSG_NOSIGNAL
flag on your send(2) call.Example replacing
write(...)
bysend(...,MSG_NOSIGNAL)
(see nobar's comment)在此 帖子 中,我描述了 Solaris 情况下的可能解决方案SO_NOSIGPIPE 和 MSG_NOSIGNAL 都不可用。
示例代码位于 https://github.com/kroki/XProbes/斑点/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156
In this post I described possible solution for Solaris case when neither SO_NOSIGPIPE nor MSG_NOSIGNAL is available.
Example code at https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156
Linux手册说:
但对于 Ubuntu 12.04 来说这是不对的。 我针对这种情况编写了一个测试,并且总是收到 EPIPE 而没有 SIGPIPE。 如果我尝试第二次写入同一个损坏的套接字,则会生成 SIGPIPE。 所以你不需要忽略 SIGPIPE,如果这个信号发生,它意味着你的程序中有逻辑错误。
Linux manual said:
But for Ubuntu 12.04 it isn't right. I wrote a test for that case and I always receive EPIPE withot SIGPIPE. SIGPIPE is genereated if I try to write to the same broken socket second time. So you don't need to ignore SIGPIPE if this signal happens it means logic error in your program.
我在做一个练习时遇到了这个问题,要求我们在 ac 程序中从 shell 复制管道。 当 before 命令尝试写入管道但 after 命令不是有效命令时,after 命令终止,并且我在 before 命令上收到了 SIGPIPE。
我能够通过在让它死之前排出命令后的所有输入来解决这个问题。
I was having this problem doing an exercise that require us to replicate the pipe from shell in a c program. When a before command was trying to write to pipe but the after command was not a valid command the after command died and I received a SIGPIPE on before command.
I was able to solve this by draining all input from after command before letting it die.
要么按照每个人的要求禁用 sigpipe,要么捕获并忽略错误。
是的,使用 select()。
您需要选择读取位。 您可能可以忽略写入位。
当远端关闭其文件句柄时,select 会告诉您有数据可供读取。 当你去读取它时,你会得到 0 个字节,这就是操作系统告诉你文件句柄已经关闭的方式。
唯一不能忽略写入位的情况是,如果您正在发送大量数据,并且存在另一端积压的风险,这可能会导致缓冲区被填满。 如果发生这种情况,则尝试写入文件句柄可能会导致程序/线程阻塞或失败。 在写入之前测试 select 可以保护您免受这种情况的影响,但它不能保证另一端是健康的或您的数据将会到达。
请注意,您可以从 close() 以及写入时获取 sigpipe。
关闭刷新所有缓冲的数据。 如果另一端已经关闭,则关闭将失败,并且您将收到一个 sigpipe。
如果您使用的是缓冲 TCPIP,那么成功写入仅意味着您的数据已排队等待发送,并不意味着它已经发送。 在成功调用 close 之前,您不知道数据已发送。
Sigpipe 告诉您出了问题,但它不会告诉您出了什么问题,或者您应该采取什么措施。
Either disable sigpipes as per everybody, or catch and ignore the error.
Yes, use select().
You need to select on the read bits. You can probably ignore the write bits.
When the far end closes its file handle, select will tell you that there is data ready to read. When you go and read that, you will get back 0 bytes, which is how the OS tells you that the file handle has been closed.
The only time you can't ignore the write bits is if you are sending large volumes, and there is a risk of the other end getting backlogged, which can cause your buffers to fill. If that happens, then trying to write to the file handle can cause your program/thread to block or fail. Testing select before writing will protect you from that, but it doesn't guarantee that the other end is healthy or that your data is going to arrive.
Note that you can get a sigpipe from close(), as well as when you write.
Close flushes any buffered data. If the other end has already been closed, then close will fail, and you will receive a sigpipe.
If you are using buffered TCPIP, then a successful write just means your data has been queued to send, it doesn't mean it has been sent. Until you successfully call close, you don't know that your data has been sent.
Sigpipe tells you something has gone wrong, it doesn't tell you what, or what you should do about it.
在现代 POSIX 系统(即 Linux)下,您可以使用 sigprocmask() 函数。
如果您想稍后恢复之前的状态,请确保将
old_state
保存在安全的地方。 如果多次调用该函数,则需要使用堆栈或仅保存第一个或最后一个old_state
...或者可能有一个使用sigdelset()
函数。有关详细信息,请阅读手册页。
Under a modern POSIX system (i.e. Linux), you can use the
sigprocmask()
function.If you want to restore the previous state later, make sure to save the
old_state
somewhere safe. If you call that function multiple times, you need to either use a stack or only save the first or lastold_state
... or maybe have a function which removes a specific blocked signal using thesigdelset()
function.For more info read the man page.