如何在生成SIGPIPE错误的对象中处理它

How to handle a SIGPIPE error inside the object that generated it?

本文关键字:对象 处理 错误 SIGPIPE      更新时间:2023-10-16

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

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

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

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

为了澄清,下面是当前的架构:

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

那么我如何使我的MainWidget对象创建在int main()处理信号,使他可以调用timerSendData->stop() ?

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

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

  2. 执行可能引发SIGPIPE异常的操作。

  3. 以零超时调用sigtimedwait以消耗任何挂起的SIGPIPE信号。

  4. 恢复原始信号掩码(如果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阻塞(注意:阻塞是线程本地的,不影响其他线程),并可能使SIGPIPE挂起(处于阻塞状态)。然后,调用者将负责在必要时恢复它,因此想要 SIGPIPE的极少数调用者可以通过解除信号阻塞来获得它(但在函数完成之后),而大多数调用者可以愉快地让它被阻塞。阻塞代码像上面那样工作,去掉了sigtimedwait/unblocking部分。这与Maxim的答案类似,只是影响是线程局部的,因此是线程安全的。

现在我确实学习了如何简单地处理信号:创建一个接收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