如何防止 SIGPIPE(或正确处理它们)

发布于 2024-07-05 22:42:50 字数 273 浏览 13 评论 0 原文

我有一个小型服务器程序,它接受 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

毅然前行 2024-07-12 22:42:50

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

我相信这是正确的。 您想知道另一端何时关闭其描述符,这就是 SIGPIPE 告诉您的。

山姆

Or should I just catch the SIGPIPE with a handler and ignore it?

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

芯好空 2024-07-12 22:42:50

您无法阻止管道远端的进程退出,如果它在您完成写入之前退出,您将收到 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).

白龙吟 2024-07-12 22:42:50

本地处理 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 信号,但在尝试使用套接字时会出现读/写错误,因此您需要检查一下。

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:

// 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);

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.

青春如此纠结 2024-07-12 22:42:50

您通常希望忽略 SIGPIPE 并直接在代码中处理错误。 这是因为 C 中的信号处理程序对其功能有很多限制。

最可移植的方法是将 SIGPIPE 处理程序设置为 SIG_IGN。 这将防止任何套接字或管道写入引起 SIGPIPE 信号。

要忽略 SIGPIPE 信号,请使用以下代码:

signal(SIGPIPE, SIG_IGN);

如果您使用 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 to SIG_IGN. This will prevent any socket or pipe write from causing a SIGPIPE signal.

To ignore the SIGPIPE signal, use the following code:

signal(SIGPIPE, SIG_IGN);

If you're using the send() call, another option is to use the MSG_NOSIGNAL option, which will turn the SIGPIPE behavior off on a per call basis. Note that not all operating systems support the MSG_NOSIGNAL flag.

Lastly, you may also want to consider the SO_NOSIGPIPE socket flag that can be set with setsockopt() on some operating systems (e.g. those derived from BSD). This will prevent SIGPIPE from being caused by writes just to the sockets it is set on.

挽你眉间 2024-07-12 22:42:50

另一种方法是更改​​套接字,使其永远不会在 write() 上生成 SIGPIPE。 这在库中更方便,您可能不需要 SIGPIPE 的全局信号处理程序。

在大多数基于 BSD(MacOS、FreeBSD...)的系统上(假设您使用的是 C/C++),您可以通过以下方式执行此操作:

int set = 1;
setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));

生效后,将返回 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:

int set = 1;
setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));

With this in effect, instead of the SIGPIPE signal being generated, EPIPE will be returned.

岁吢 2024-07-12 22:42:50

我来晚了,但是 SO_NOSIGPIPE 不可移植,并且可能无法在您的系统上运行(这似乎是 BSD 的问题)。

如果您使用的是没有 SO_NOSIGPIPE 的 Linux 系统,一个不错的选择是在 send(2) 调用上设置 MSG_NOSIGNAL 标志。

write(...) 替换为 send(...,MSG_NOSIGNAL) 的示例(请参阅 nobar 的评论)

char buf[888];
//write( sockfd, buf, sizeof(buf) );
send(    sockfd, buf, sizeof(buf), MSG_NOSIGNAL );

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 the MSG_NOSIGNAL flag on your send(2) call.

Example replacing write(...) by send(...,MSG_NOSIGNAL) (see nobar's comment)

char buf[888];
//write( sockfd, buf, sizeof(buf) );
send(    sockfd, buf, sizeof(buf), MSG_NOSIGNAL );
隱形的亼 2024-07-12 22:42:50

在此 帖子 中,我描述了 Solaris 情况下的可能解决方案SO_NOSIGPIPE 和 MSG_NOSIGNAL 都不可用。

相反,我们必须暂时抑制当前执行库代码的线程中的 SIGPIPE。 执行此操作的方法如下:为了抑制 SIGPIPE,我们首先检查它是否处于挂起状态。 如果是的话,这意味着它被阻塞在这个线程中,我们什么也不做。 如果库生成额外的 SIGPIPE,它将与待处理的 SIGPIPE 合并,这是一项无操作。 如果 SIGPIPE 没有挂起,那么我们会在该线程中阻塞它,并检查它是否已经被阻塞。 然后我们就可以自由地执行我们的写入操作。 当我们要将 SIGPIPE 恢复到其原始状态时,我们执行以下操作:如果 SIGPIPE 最初处于挂起状态,则我们不执行任何操作。 否则我们检查它现在是否处于待处理状态。 如果确实如此(这意味着 out 操作已生成一个或多个 SIGPIPE),则我们在此线程中等待它,从而清除其挂起状态(为此,我们使用零超时的 sigtimedwait() ;这是为了避免阻塞恶意用户手动向整个进程发送 SIGPIPE 的情况:在这种情况下,我们将看到它处于待处理状态,但其他线程可能会在我们进行更改之前处理它以等待它)。 清除挂起状态后,我们在该线程中取消阻止 SIGPIPE,但前提是它最初未被阻止。

示例代码位于 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.

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.

Example code at https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156

强者自强 2024-07-12 22:42:50

Linux手册说:

EPIPE 面向连接的本端已关闭
插座。 在这种情况下,进程还将收到 SIGPIPE
除非设置了 MSG_NOSIGNAL。

但对于 Ubuntu 12.04 来说这是不对的。 我针对这种情况编写了一个测试,并且总是收到 EPIPE 而没有 SIGPIPE。 如果我尝试第二次写入同一个损坏的套接字,则会生成 SIGPIPE。 所以你不需要忽略 SIGPIPE,如果这个信号发生,它意味着你的程序中有逻辑错误。

Linux manual said:

EPIPE The local end has been shut down on a connection oriented
socket. In this case the process will also receive a SIGPIPE
unless MSG_NOSIGNAL is set.

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.

三生池水覆流年 2024-07-12 22:42:50

我在做一个练习时遇到了这个问题,要求我们在 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.

橘虞初梦 2024-07-12 22:42:50

防止崩溃的最佳做法是什么?

要么按照每个人的要求禁用 sigpipe,要么捕获并忽略错误。

有没有办法检查线路的另一端是否仍在阅读?

是的,使用 select()。

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

您需要选择读取位。 您可能可以忽略写入位。

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

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

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

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

如果您使用的是缓冲 TCPIP,那么成功写入仅意味着您的数据已排队等待发送,并不意味着它已经发送。 在成功调用 close 之前,您不知道数据已发送。

Sigpipe 告诉您出了问题,但它不会告诉您出了什么问题,或者您应该采取什么措施。

What's the best practice to prevent the crash here?

Either disable sigpipes as per everybody, or catch and ignore the error.

Is there a way to check if the other side of the line is still reading?

Yes, use select().

select() doesn't seem to work here as it always says the socket is writable.

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.

青丝拂面 2024-07-12 22:42:50

在现代 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...或者可能有一个使用 sigdelset() 函数。

有关详细信息,请阅读手册页

Under a modern POSIX system (i.e. Linux), you can use the sigprocmask() function.

#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 ...
}

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 last old_state... or maybe have a function which removes a specific blocked signal using the sigdelset() function.

For more info read the man page.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文