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

时间:2014-07-23 20:53:47

标签: c++ c sockets send

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

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

现在我确实学会了如何简单地处理信号:创建一个方法,在main()或global(内部)中接收int和使用信号(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->stop()

2 个答案:

答案 0 :(得分:4)

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

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

  2. 执行可能引发SIGPIPE

  3. 的操作
  4. 使用零超时调用sigtimedwait以消耗任何待处理的SIGPIPE信号。

  5. 恢复原始信号掩码(如果之前未阻止,则取消阻止SIGPIPE。)

  6. 以下是使用此方法的一些示例代码的尝试,以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的答案类似,只是影响是线程本地的,因此是线程安全的。

答案 1 :(得分:3)

  

现在我确实学会了如何简单地处理信号:创建一个接收int和使用信号的方法(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