У меня есть небольшая серверная программа, которая принимает соединения через TCP или локальный сокет UNIX, читает простую команду и, в зависимости от команды, отправляет ответ. Проблема в том, что клиент может быть не заинтересован в ответе иногда и выходит рано, поэтому запись в этот сокет вызовет SIGPIPE и приведет к сбою моего сервера. Какова лучшая практика, чтобы предотвратить аварию здесь? Есть ли способ проверить, читает ли другая сторона строки? (select () здесь не работает, так как всегда говорит, что сокет доступен для записи). Или я должен просто поймать SIGPIPE с обработчиком и игнорировать его?
Другой способ - изменить сокет, чтобы он никогда не генерировал SIGPIPE при записи (). Это более удобно в библиотеках, где вам может не потребоваться глобальный обработчик сигналов для SIGPIPE.
В большинстве систем на основе BSD (MacOS, FreeBSD ...) (при условии, что вы используете C / C ++) вы можете сделать это с помощью:
При этом вместо генерируемого сигнала SIGPIPE будет возвращен EPIPE.
источник
Я супер опоздал на вечеринку, но
SO_NOSIGPIPE
не переносим, и может не работать в вашей системе (кажется, это вещь BSD).Хорошей альтернативой, если вы работаете, скажем, в системе Linux без,
SO_NOSIGPIPE
было бы установитьMSG_NOSIGNAL
флаг в вашем вызове send (2).Пример замены
write(...)
наsend(...,MSG_NOSIGNAL)
(см. Комментарий nobar )источник
QTcpSocket
объект Qt, чтобы обернуть использование сокетов, мне пришлось заменитьwrite
вызов метода операционной системойsend
(используяsocketDescriptor
метод). Кто-нибудь знает более чистую опцию, чтобы установить эту опцию вQTcpSocket
классе?В этом посте я описал возможное решение для случая Solaris, когда ни SO_NOSIGPIPE, ни MSG_NOSIGNAL не доступны.
Пример кода на https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156
источник
Ручка SIGPIPE Локально
Обычно лучше обрабатывать ошибку локально, а не в глобальном обработчике сигнальных событий, поскольку локально у вас будет больше контекста относительно того, что происходит, и какой путь предпринять.
У меня есть коммуникационный уровень в одном из моих приложений, который позволяет моему приложению взаимодействовать с внешним аксессуаром. Когда происходит ошибка записи, я выбрасываю исключение на уровне связи и позволяю ему всплыть в блоке catch для его обработки.
Код:
Код для игнорирования сигнала SIGPIPE, чтобы вы могли обрабатывать его локально:
Этот код предотвратит появление сигнала SIGPIPE, но при попытке использовать сокет вы получите ошибку чтения / записи, поэтому вам нужно будет проверить это.
источник
Вы не можете предотвратить выход процесса на дальнем конце канала, и если он завершится до завершения записи, вы получите сигнал SIGPIPE. Если вы отправите сигнал SIG_IGN, то ваша запись вернется с ошибкой - и вы должны заметить и отреагировать на эту ошибку. Просто перехватывать и игнорировать сигнал в обработчике - не очень хорошая идея - вы должны заметить, что канал теперь не функционирует, и изменить поведение программы, чтобы она больше не записывала данные в канал (поскольку сигнал будет сгенерирован снова и проигнорирован снова, и вы попробуете снова, и весь процесс может продолжаться долго и тратить много ресурсов процессора).
источник
Я считаю, что это правильно. Вы хотите знать, когда другой конец закрыл свой дескриптор, и это то, что SIGPIPE говорит вам.
Сэм
источник
В руководстве по Linux сказано:
Но для Ubuntu 12.04 это не правильно. Я написал тест для этого случая, и я всегда получаю EPIPE без SIGPIPE. SIGPIPE генерируется, если я пытаюсь записать в тот же сломанный сокет второй раз. Поэтому вам не нужно игнорировать SIGPIPE, если этот сигнал происходит, это означает логическую ошибку в вашей программе.
источник
Либо отключите sigpipes для всех, либо поймайте и проигнорируйте ошибку.
Да, используйте select ().
Вы должны выбрать биты для чтения . Вы, вероятно, можете игнорировать биты записи .
Когда дальний конец закроет свой дескриптор файла, select скажет вам, что есть данные, готовые для чтения. Когда вы пойдете и прочитаете это, вы получите обратно 0 байтов, и именно так ОС сообщает вам, что дескриптор файла был закрыт.
Единственный раз, когда вы не можете игнорировать биты записи, это если вы отправляете большие тома, и существует риск того, что другой конец будет засорен, что может привести к заполнению ваших буферов. Если это произойдет, то попытка записи в дескриптор файла может привести к блокировке или неудаче вашей программы / потока. Тестирование select перед записью защитит вас от этого, но это не гарантирует, что другой конец исправен или что ваши данные будут доставлены.
Обратите внимание, что вы можете получить sigpipe из close (), а также когда вы пишете.
Закрыть сбрасывает любые буферизованные данные. Если другой конец уже был закрыт, то закрыть не удастся, и вы получите сигпайп.
Если вы используете буферизованный TCPIP, то успешная запись означает, что ваши данные были поставлены в очередь для отправки, но это не значит, что они были отправлены. Пока вы успешно не позвоните близко, вы не знаете, что ваши данные были отправлены.
Сигпайп говорит вам, что что-то пошло не так, он не говорит вам, что или что вы должны с этим делать.
источник
В современной системе POSIX (например, Linux) вы можете использовать эту
sigprocmask()
функцию.Если вы хотите восстановить предыдущее состояние позже, обязательно сохраните его в
old_state
безопасном месте. Если вы вызываете эту функцию несколько раз, вам нужно либо использовать стек, либо сохранить только первый или последнийold_state
... или, возможно, иметь функцию, которая удаляет определенный заблокированный сигнал.Для получения дополнительной информации прочитайте справочную страницу .
источник
sigprocmask
добавляет в набор заблокированных сигналов, так что вы можете сделать все это одним вызовом.old_state
чтобы я мог восстановить его позже, если захочу. Если вы знаете, что вам не нужно когда-либо восстанавливать состояние, нет необходимости читать и хранить его таким образом.sigprocmask()
чтения старого состояния, вызов дляsigaddset
его изменения, второй вызов дляsigprocmask()
его записи. Вы можете удалить первый вызов, выполнить инициализациюsigemptyset(&set)
и изменить второй вызов наsigprocmask(SIG_BLOCK, &set, &old_state)
.