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

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

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

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

// 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()

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

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

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

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

  4. 恢复原始信号掩码(如果之前未阻塞,则解锁SIGPIPE )。

以下是使用此方法的一些示例代码的尝试,以write的纯包装器的forms避免了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和使用信号的方法(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