Как обрабатывать ошибку SIGPIPE внутри объекта, который сгенерировал ее?

У меня есть два приложения, один сервер и другой клиент, оба написанные на C ++ и Qt, но оба они также используют библиотеку C, которая использует методы сокета C для выполнения связи сокетов между ними (и все это в Linux).

Когда оба они подключены, и я закрываю клиент, когда сервер пытается отправить ему новое сообщение, он получает ошибку SIGPIPE и закрывается. Я провел некоторое исследование в Интернете и в SO, чтобы узнать, как я могу создать обработчик для SIGPIPE, поэтому вместо закрытия приложения я рассказываю таймерам, которые постоянно отправляют информацию для остановки.

Теперь я научился просто обрабатывать сигнал: создать метод, который получает сигнал int и use (SIGPIPE, myMethod) внутри main () или глобальный (примечание: узнал, что из SO и да, я знаю, что signal () устарел ).

Но проблема в том, что таким образом я не могу остановить отправку информации мертвому клиенту, поскольку метод, обрабатывающий сигнал, должен быть либо вне classа, который отправляет сообщение, либо статический метод, t иметь доступ к моему серверному объекту.

Чтобы уточнить, вот текущая архитектура:

//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"; } - 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 является уродливым, но его можно обрабатывать таким образом, чтобы он был полностью инкапсулирован, streamобезопасен и не влияет ни на что, кроме кода, делающего запись, которая может вызвать SIGPIPE . Общий метод:

    1. Блок SIGPIPE с pthread_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; } 

    Он непроверен (даже не скомпилирован) и может потребоваться незначительные исправления, но, надеюсь, имеет смысл. Очевидно, что для эффективности вы хотели бы сделать это с большей степенью детализации, чем одиночные вызовы write (например, вы могли бы заблокировать SIGPIPE в течение всей функции библиотеки до тех пор, пока она не вернется к внешнему вызывающему абоненту).

    Альтернативный дизайн будет просто блокировать SIGPIPE и не разблокировать его и документировать в интерфейсе функции, что он блокирует SIGPIPE (обратите внимание: блокировка является локальной и не влияет на другие streamи) и, возможно, оставляет SIGPIPE ожидании (в заблокированном состоянии) , Затем вызывающий абонент будет отвечать за его восстановление, если это необходимо, поэтому редкий вызывающий абонент, который хочет , чтобы SIGPIPE мог его получить (но после завершения вашей функции), разблокировал сигнал, в то время как большинство абонентов могли с радостью оставить его заблокированным. Блокирующий код работает, как и в sigtimedwait , с sigtimedwait частью sigtimedwait / sigtimedwait . Это похоже на ответ Максима, за исключением того, что воздействие является поточно-локальным и, таким образом, streamобезопасным.

    Теперь я научился просто обрабатывать сигнал: создать метод, который получает сигнал int и use (SIGPIPE, myMethod)

    Вам просто нужно игнорировать SIGPIPE , никакой обработчик не нужен:

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

    Но проблема в том, что таким образом я не могу остановить отправку информации мертвому клиенту, поскольку метод, обрабатывающий сигнал, должен быть либо вне classа, который отправляет сообщение, либо статический метод, t иметь доступ к моему серверному объекту.

    Когда SIGPIPE игнорируется, запись в сломанное TCP-соединение возвращает код ошибки EPIPE , который вы используете для использования EPIPE , EPIPE соединение было закрыто. В идеале, shell-shell должна передавать флаг MSG_NOSIGNAL для send , так что send никогда не вызывает SIGPIPE .