如何处理生成它的对象内部的 SIGPIPE 错误?

Posted

技术标签:

【中文标题】如何处理生成它的对象内部的 SIGPIPE 错误?【英文标题】:How to handle a SIGPIPE error inside the object that generated it? 【发布时间】:2014-09-15 05:50:23 【问题描述】:

我有两个应用程序,一个服务器和另一个客户端,都是用 C++ 和 Qt 编写的,但它们都使用 C 库,该库使用 C 套接字方法在它们之间执行套接字通信(这一切都在 Linux 中)。

当它们都连接并且我关闭客户端时,当服务器尝试向它发送新消息时,它会收到一个 SIGPIPE 错误并关闭。我在网络上和 SO 中做了一些研究,看看如何为 SIGPIPE 创建一个处理程序,所以我不会关闭应用程序,而是告诉不断发送信息的计时器停止。

现在我确实学会了如何简单地处理信号:创建一个接收 int 的方法并在 main() 或 global 中使用 signal(SIGPIPE, myMethod) (注意:从 SO 学到的,是的,我知道信号( ) 已过时)。

但问题是通过这种方式我无法停止向死客户端发送信息,因为处理信号的方法需要在发送消息的类之外或静态方法,无权访问我的服务器对象。

为了澄清,这里是当前的架构:

//main.cpp

void signal_callback_handler(int signum)

    qDebug() << "Caught signal SIGPIPE" << signum << "; closing the application";

    exit(EXIT_FAILURE);


int main(int argc, char *argv[])

    QApplication app(argc, argv);
    app.setApplicationName("ConnEmulator");
    app.setApplicationVersion("1.0.0");
    app.setOrganizationName("Embrasul");
    app.setOrganizationDomain("http://www.embrasul.com.br");

    MainWidget window;
    window.show();

    /* Catch Signal Handler SIGPIPE */
    signal(SIGPIPE, signal_callback_handler);

    return app.exec();

//MainWidget类(简体)

MainWidget::MainWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::MainWidget),
    timerSendData(new QTimer(this))

    ui->setupUi(this);

    connect(timerSendData,SIGNAL(timeout()),this,SLOT(slotSendData()));

    timerSendData->start();
    //...


void MainWidget::slotSendData()

    //Prepares data
    //...

    //Here the sending message is called with send()
    if (hal_socket_write_to_client(&socket_descriptor, (u_int8_t *)buff_write, myBufferSize) == -1)
        qDebug() << "Error writting to client";

//套接字库

int hal_socket_write_to_client(socket_t *obj, u_int8_t *buffer, int size)

    struct s_socket_private * const socket_obj = (struct s_socket_private *)obj;
    int retval = send(socket_obj->client_fd, buffer, size, 0);

    if (retval < 0)
        perror("write_to_client");

    return retval;

那么我怎样才能让我在 int main() 中创建的 MainWidget 对象处理信号,以便他可以调用 timerSendData-&gt;stop()

【问题讨论】:

这不是重复的。被标记为重复的问题甚至没有提到SIGPIPE 或任何关于信号的东西;它只是处理不相关的网络错误情况。 这是关于处理 ECONNRESET,这是您在处理 SIGPIPE 时得到的。没有任何“无关”的东西。一个的答案就是另一个的答案。 @EJP:ECONNRESET是读取时可能出现的错误,而不是写入时出现的错误,与SIGPIPE无关。 【参考方案1】:

SIGPIPE 很丑陋,但可以以一种完全封装的、线程安全的方式进行处理,并且除了编写可能导致SIGPIPE 的代码之外不会影响任何东西。一般的方法是:

    pthread_sigmask(或sigprocmask,但不保证后者在多线程程序中是安全的)阻塞SIGPIPE并保存原始信号掩码。

    执行可能引发SIGPIPE的操作。

    以零超时调用 sigtimedwait 以使用任何待处理的 SIGPIPE 信号。

    恢复原来的信号掩码(如果之前解封SIGPIPE,则解封)。

以下是使用此方法的一些示例代码的尝试,其形式是对write 的纯包装器,它避免了SIGPIPE

ssize_t write_nosigpipe(int fd, void *buf, size_t len)

    sigset_t oldset, newset;
    ssize_t result;
    siginfo_t si;
    struct timespec ts = 0;

    sigemptyset(&newset);
    sigaddset(&newset, SIGPIPE);
    pthread_sigmask(SIG_BLOCK, &newset, &oldset);

    result = write(fd, buf, len);

    while (sigtimedwait(newset, &si, &ts)>=0 || errno != EAGAIN);
    pthread_sigmask(SIG_SETMASK, &oldset, 0);

    return result;

它未经测试(甚至未编译),可能需要进行一些小修复,但希望能说明问题。显然,为了提高效率,您希望在比单个 write 调用更大的粒度上执行此操作(例如,您可以在整个库函数的持续时间内阻止 SIGPIPE,直到它返回到外部调用者)。

另一种设计是简单地阻塞 SIGPIPE 并且从不解除阻塞,并在函数的接口中记录它使 SIGPIPE 阻塞(注意:阻塞是线程本地的,不会影响其他线程)并且可能会留下 @ 987654337@ 挂起(处于阻塞状态)。然后调用者将负责在必要时恢复它,因此 想要 SIGPIPE 的罕见调用者可以通过解锁信号来获得它(但在你的函数完成后),而大多数调用者可以愉快地离开它被阻止了。阻塞代码的工作方式与上面类似,删除了 sigtimedwait/unblocking 部分。这类似于 Maxim 的答案,只是影响是线程局部的,因此是线程安全的。

【讨论】:

以及如何向应用程序报告失败?鉴于单手柄不应该有副作用? @EJP:当 SIGPIPE 在生成时被阻止时,write/send 返回错误,errno 设置为 EPIPE。然后,您可以像处理任何其他写入错误一样处理此问题。 很少有库在调用 send 时这样做。学究式地解决一个不存在的问题。 那他们怎么办?对sigaction 进行非线程安全调用或依赖调用者阻止或忽略SIGPIPE?仅仅因为很少有图书馆能正确地做某事并不是反对这样做的理由。很少有库能正确处理内存不足,这使得大多数不能在严肃程序中使用的垃圾库无效。 顺便说一句,如果调用者提供了套接字 fd,那么除非库必须保证某些磁盘结构或类似结构的一致性,否则我认为库“让 SIGPIPE 发生”是非常合理的除非来电者阻止/忽略它。但是如果打开和使用套接字是库内部的(实现的一部分,而不是接口),它肯定需要防止SIGPIPE 使程序崩溃或记录它。【参考方案2】:

现在我确实学会了如何简单地处理信号:创建一个接收 int 的方法并使用 signal(SIGPIPE, myMethod)

你只需要忽略SIGPIPE,不需要处理程序:

// don't raise SIGPIPE when sending into broken TCP connections
::signal(SIGPIPE, SIG_IGN); 

但问题是通过这种方式我无法停止向死客户端发送信息,因为处理信号的方法需要在发送消息的类之外或静态方法,无权访问我的服务器对象。

SIGPIPE 被忽略写入断开的 TCP 连接时,将返回错误代码 EPIPE,您使用的套接字包装器应该像连接已关闭一样处理该错误代码。理想情况下,套接字包装器应该将MSG_NOSIGNAL 标志传递给send,以便send 永远不会引发SIGPIPE

【讨论】:

由于SIGPIPE 的信号配置是全局的,这是不合适的;程序的另一部分可能希望生成信号并且可能具有信号处理程序。 @R.真的。我还没有看到引发 SIGPIPE 的库,但如果我这样做了,我会避免或修补它们。 @DietrichEpp:如果执行忽略的代码是主程序并且它知道它想一直忽略SIGPIPE,Maxim 的解决方案是合适的,但它不是您可以安全放入的代码图书馆。我的方法适用于任何一种。 @R.忽略 SIG_PIPE 是 99% 的程序如何做到的。不要再抱怨有人提供了与你不同的答案。 @EJP:我没有抱怨。我正在提供有关不同选项的优缺点的有用信息。 SO问题和答案应该在未来有用,而不仅仅是对OP,因此扩展它们是提供有用资源的一部分。如您所见,我同意 Maxim 的观点,即MSG_NOSIGNAL 是可能的最佳方式。

以上是关于如何处理生成它的对象内部的 SIGPIPE 错误?的主要内容,如果未能解决你的问题,请参考以下文章

如果前端 Tomcat 被杀死,C++ 服务器将终止。错误“收到未捕获的信号 [13] - SIGPIPE”

SIGPIPE 写入 EBS Docker 应用程序中的关闭管道错误

razorpay_flutter 在 PUBSPEC.YML 文件中集成它显示错误

设计模式(一)面向对象设计原则

Java Socket 怎么忽略SIGPIPE信号

类型别名,auto,decltype