我正在为 Linux 上的 TCP 连接编写 C++ SSL 服务器。 当程序使用
SSL_write()
写入关闭的管道时,会抛出 SIGPIPE-Exception,导致程序关闭。我知道这是正常行为。但是当对等方没有正确关闭连接时,程序不应该总是死掉。
我已经用谷歌搜索了很多,并尝试了几乎所有我发现的东西,但似乎没有什么对我有用。
signal(SIGPIPE,SIG_IGN)
不起作用 - 仍然会抛出异常(signal(SIGPIPE, SomeKindOfHandler)
相同。
gdb 输出:
Program received signal SIGPIPE, Broken pipe.
0x00007ffff6b23ccd in write () from /lib/x86_64-linux-gnu/libpthread.so.0
(gdb) where
#0 0x00007ffff6b23ccd in write () from /lib/x86_64-linux-gnu/libpthread.so.0
#1 0x00007ffff7883835 in ?? () from /lib/x86_64-linux-gnu/libcrypto.so.1.0.0
#2 0x00007ffff7881687 in BIO_write () from /lib/x86_64-linux-gnu/libcrypto.so.1.0.0
#3 0x00007ffff7b9d3e0 in ?? () from /lib/x86_64-linux-gnu/libssl.so.1.0.0
#4 0x00007ffff7b9db04 in ?? () from /lib/x86_64-linux-gnu/libssl.so.1.0.0
#5 0x000000000042266a in NetInterface::SendToSubscribers(bool) () at ../Bether/NetInterface.h:181
#6 0x0000000000425834 in main () at ../Bether/main.cpp:111
关于守则:
我正在使用一个正在等待新连接并接受它们的线程。然后,线程将连接信息(BIO 和 SSL)放入
NetInterface
类内的静态映射中。
每 5 秒从 NetInterface::sendTOSubscribers()
执行一次 main()
。该函数访问静态映射并将数据发送到其中的每个连接。这个函数也是 SIGPIPE 的来源。
我在 signal(SIGPIPE,SIG_IGN)
(显然在 5 秒循环之前)和 main()
中使用了 NetInterface::SendToSubscribers()
,但它在任何地方都不起作用。
感谢您的帮助!
您必须调用函数 sigaction 来更改此行为,要么忽略 SIGPIPE,要么使用您自己的信号处理程序以特定方式处理它。请不要使用函数信号,它已经过时了。
http://man7.org/linux/man-pages/man2/sigaction.2.html
一种方法(我还没有编译此代码,但应该是这样的):
void sigpipe_handler(int signal)
{
...
}
int main()
{
struct sigaction sh;
struct sigaction osh;
sh.sa_handler = &sigpipe_handler; //Can set to SIG_IGN
// Restart interrupted system calls
sh.sa_flags = SA_RESTART;
// Block every signal during the handler
sigemptyset(&sh.sa_mask);
if (sigaction(SIGPIPE, &sh, &osh) < 0)
{
return -1;
}
...
}
如果程序是多线程的,情况会略有不同,因为您对哪个线程将接收信号的控制较少。这取决于信号的类型。对于 SIGPIPE,它将被发送到生成该信号的 pthread。尽管如此,sigaction 应该可以正常工作。
可以在主线程中设置掩码,随后创建的所有 pthread 都将继承该信号掩码。否则,可以在每个线程中设置信号掩码。
sigset_t blockedSignal;
sigemptyset(&blockedSignal);
sigaddset(&blockedSignal, SIGPIPE);
pthread_sigmask(SIG_BLOCK, &blockedSignal, NULL);
但是,如果您阻止该信号,则该信号将等待该进程,并且一旦有可能就会被传递。对于这种情况,请在线程末尾使用 sigtimedwait。在主线程或生成 SIGPIPE 的线程中设置的 sigaction 也应该有效。
我找到了解决方案,它与 pthread_sigmask 一起使用。
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGPIPE);
if (pthread_sigmask(SIG_BLOCK, &set, NULL) != 0)
return -1;
感谢大家的帮助!
我的问题是,我正在开发一个可移植库,至少可以在线程环境中的 Linux、macOS/OpenBSD 和 Microsoft Windows 平台上使用并使用 OpenSSL。
signal(SIGPIPE, SIG_IGN)
不是一个选项,因为它会修改用户的进程环境。我可以使用令人困惑的大小写区别,在 macOS/OpenBSD 上使用 SO_NOSIGPIPE
,在 Linux 平台上使用 MSG_NOSIGNAL
,而在 MS Windows 上不执行任何操作。这仅适用于系统调用 send()
,但 OpenSSL 使用 write()
代替。自 OpenSSL 开发多年以来,当 OpenSSL 与套接字一起使用时的 SIGPIPE 问题一直悬而未决。
所以我查看了 kroki 的一般解决方案。他写道,他使用 sigtimedwait()
,但这在 macOS/OpenBSD 上不可用。幸运的是,它可以通过
sigpending()
和
sigwait()
来解决。查看 krokis 示例以了解其工作原理,我创建了一个仅包含构造函数和析构函数的“作用域”类 CSigpipe。有关详细信息,请查看参考资料。这是课程:头文件 sigpipe.hpp
#ifndef SAMPLE_SIGPIPE_HPP
#define SAMPLE_SIGPIPE_HPP
#ifndef _MSC_VER
#include <csignal>
namespace sigpipe {
class CSigpipe_scoped {
public:
CSigpipe_scoped();
~CSigpipe_scoped();
private:
bool m_sigpipe_pending;
bool m_sigpipe_unblock;
};
} // namespace sigpipe
#define SCOPED_NO_SIGPIPE sigpipe::CSigpipe_scoped sigpipe;
#else
// This will the ported program also compile on win32 without error.
#define SCOPED_NO_SIGPIPE
#endif // #ifndef _MSC_VER
#endif // SAMPLE_SIGPIPE_HPP
源文件sigpipe.cpp
#ifndef _MSC_VER
#include "sigpipe.hpp"
namespace sigpipe {
CSigpipe_scoped::CSigpipe_scoped() {
/* We want to ignore possible SIGPIPE that we can generate on write. SIGPIPE
* is delivered *synchronously* and *only* to the thread doing the write. So
* if it is reported as already pending (which means the thread blocks it),
* then we do nothing: if we generate SIGPIPE, it will be merged with the
* pending one (there's no queuing), and that suits us well. If it is not
* pending, we block it in this thread (and we avoid changing signal action,
* because it is per-process). */
sigset_t pending;
sigemptyset(&pending);
sigpending(&pending);
m_sigpipe_pending = sigismember(&pending, SIGPIPE);
if (!m_sigpipe_pending) {
sigset_t sigpipe_mask;
sigemptyset(&sigpipe_mask);
sigaddset(&sigpipe_mask, SIGPIPE);
sigset_t blocked;
sigemptyset(&blocked);
pthread_sigmask(SIG_BLOCK, &sigpipe_mask, &blocked);
/* Maybe is was blocked already? */
m_sigpipe_unblock = !sigismember(&blocked, SIGPIPE);
}
}
CSigpipe_scoped::~CSigpipe_scoped() {
/* If SIGPIPE was pending already we do nothing. Otherwise, if it become
* pending (i.e., we generated it), then we sigwait() it (thus clearing
* pending status). Then we unblock SIGPIPE, but only if it were us who
* blocked it. */
if (!m_sigpipe_pending) {
sigset_t sigpipe_mask;
sigemptyset(&sigpipe_mask);
sigaddset(&sigpipe_mask, SIGPIPE);
// I cannot use sigtimedwait() because it isn't available on
// macOS/OpenBSD. I workaround it with sigpending() and sigwait() to
// ensure that sigwait() never blocks.
sigset_t pending;
while (true) {
/* Protect ourselves from a situation when SIGPIPE was sent by
* the user to the whole process, and was delivered to other
* thread before we had a chance to wait for it. */
sigemptyset(&pending);
sigpending(&pending);
if (sigismember(&pending, SIGPIPE)) {
int sig; // Only return buffer, not used.
sigwait(&sigpipe_mask, &sig);
} else
break;
}
if (m_sigpipe_unblock)
pthread_sigmask(SIG_UNBLOCK, &sigpipe_mask, nullptr);
}
}
} // namespace sigpipe
#endif
一个简单的主示例程序。
#include <iostream>
#ifdef _MSC_VER
// This simple test doesn't make sense on Microsoft Windows because it doesn't
// support sigpipe. It doesn't know the signal name SIGPIPE and never raise a
// signal with broken pipe on socket write(). But it compiles the object
// "instantiation" without error to be portable.
#include "sigpipe.hpp"
int main() {
SCOPED_NO_SIGPIPE
std::cout << "Nothing done.\n";
}
#else
#include "sigpipe.hpp"
namespace {
volatile std::sig_atomic_t gSignalStatus;
}
void signal_handler(int signal) { gSignalStatus = signal; }
int main() {
// Install a signal handler
std::signal(SIGPIPE, signal_handler);
// Suppress signal
std::cout << "SignalValue: " << gSignalStatus << '\n';
std::cout << "Suppress signal SIGPIPE\nRaise SIGPIPE = " << SIGPIPE << '\n';
{
SCOPED_NO_SIGPIPE
if (std::raise(SIGPIPE) != 0)
return EXIT_FAILURE; // Signal will be restored
} // Signal will be restored
if (gSignalStatus != 0)
std::cout << "Signal delivered, signal_handler executed." << '\n';
std::cout << "SignalValue: " << gSignalStatus << '\n';
// Not suppressing signal
std::cout << "\nRaise SIGPIPE = " << SIGPIPE << '\n';
std::raise(SIGPIPE);
if (gSignalStatus != 0)
std::cout << "Signal delivered, signal_handler executed." << '\n';
std::cout << "SignalValue: " << gSignalStatus << '\n';
return EXIT_SUCCESS;
}
#endif
在 Linux 上编译:
~$ g++ -std=c++17 -Wall -Wpedantic -Wextra -Werror -Wuninitialized -Wsuggest-override -Wdeprecated sigpipe.cpp main.cpp -lpthread
Linux 上简单示例的输出:
~$ a.out
SignalValue: 0
Suppress signal SIGPIPE
Raise SIGPIPE = 13
SignalValue: 0
Raise SIGPIPE = 13
Signal delivered, signal_handler executed.
SignalValue: 13
参考资料:
SIGPIPE 以及如何忽略它
忽略SIGPIPE而不影响进程中的其他线程
如何防止SIGPIPE(或正确处理它们)
信号、线程、SIGPIPE、sigpending(有趣!)