C++ 和 OpenSSL:在封闭管道中写入时出现 SIGPIPE

问题描述 投票:0回答:3

我正在为 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()
,但它在任何地方都不起作用。

感谢您的帮助!

c++ linux openssl sigpipe
3个回答
4
投票

您必须调用函数 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 也应该有效。


2
投票

我找到了解决方案,它与 pthread_sigmask 一起使用。

sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGPIPE);
if (pthread_sigmask(SIG_BLOCK, &set, NULL) != 0)
    return -1;

感谢大家的帮助!


0
投票

我的问题是,我正在开发一个可移植库,至少可以在线程环境中的 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(有趣!)

© www.soinside.com 2019 - 2024. All rights reserved.