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

Mom*_*gil 5 c c++ sockets send

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

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

现在我确实学习了如何简单地处理信号:创建一个接收int并在main()或global内使用signal(SIGPIPE,myMethod)的方法(注意:从SO中学到的,是的,我知道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();
}
Run Code Online (Sandbox Code Playgroud)

// 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";
}
Run Code Online (Sandbox Code Playgroud)

//套接字库

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;
}
Run Code Online (Sandbox Code Playgroud)

那么,如何使在内部创建的MainWidget对象能够int main()处理信号以便他调用timerSendData->stop()呢?

R..*_*R.. 6

SIGPIPE很难看,但是可以用完全封装的,线程安全的方式处理,除了影响可能导致的写代码之外,不影响其他任何事情SIGPIPE。通用方法是:

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

  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;
}
Run Code Online (Sandbox Code Playgroud)

它未经测试(甚至没有编译),可能需要进行较小的修复,但希望可以理解这一点。显然,为了提高效率,您希望比单次write调用具有更大的粒度(例如,您可以SIGPIPE在整个库函数的持续时间内进行阻塞,直到返回给外部调用者为止)。

另一种设计是简单地阻止SIGPIPE它,并且永远不要解除它,并在函数的接口中记录它保持SIGPIPE被阻止状态(注意:阻止是线程本地的,不会影响其他线程),并且有可能SIGPIPE处于挂起状态(处于阻止状态)。然后,如果有必要,调用者将负责恢复它,因此想要 的罕见调用者SIGPIPE可以通过解除阻止信号来获取它(但是在函数完成之后),而大多数调用者可以高兴地将其保留为阻止状态。阻止代码的工作原理与上述相同,但sigtimedwait删除了/ unblocking部分。这与Maxim的答案类似,除了影响是线程局部的,因此是线程安全的。