У меня есть два приложения, один сервер и другой клиент, оба написанные на 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
. Общий метод:
Блок SIGPIPE
с pthread_sigmask
(или sigprocmask
, но последний не гарантированно безопасен в многопоточных программах) и сохранит исходную сигнальную маску.
Выполните операцию, которая может вызвать SIGPIPE
.
Вызовите sigtimedwait
с нулевым таймаутом, чтобы использовать любой ожидающий сигнал SIGPIPE
.
Восстановите исходную сигнальную маску (разблокировка 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
.