Как предотвратить Сигпайпы (или обращаться с ними правильно)
У меня есть небольшая серверная программа, которая принимает соединения по TCP или локальному сокету UNIX, читает простую команду и, в зависимости от команды, отправляет ответ. Проблема в том, что клиент может иногда не интересоваться ответом и выходить рано, поэтому запись в этот сокет вызовет SIGPIPE и вызовет сбой моего сервера. Какая лучшая практика для предотвращения аварии здесь? Есть ли способ проверить, если другая сторона линии все еще читает? (select() кажется, не работает здесь, как это всегда говорит, что сокет доступен для записи). Или я должен просто поймать сигнал sigpipe, с обработчиком и игнорировать его?
10 ответов:
вы вообще хотите игнорировать
SIGPIPE
и обрабатывать ошибку непосредственно в коде. Это связано с тем, что обработчики сигналов в C имеют много ограничений на то, что они могут делать.самый портативный способ сделать это-установить
SIGPIPE
обработчикSIG_IGN
. Это предотвратит любую запись сокета или канала от причиненияSIGPIPE
сигнал.игнорировать
SIGPIPE
сигнал, используйте следующий код:signal(SIGPIPE, SIG_IGN);
если вы используете
send()
звонок, другой вариант заключается в использованииMSG_NOSIGNAL
вариант, который превратитSIGPIPE
поведение на каждого вызова. Обратите внимание, что не все операционные системы поддерживаютMSG_NOSIGNAL
флаг.наконец, вы также можете рассмотреть
SO_SIGNOPIPE
флаг сокета, который можно установить с помощьюsetsockopt()
на некоторых операционных системах. Это предотвратитSIGPIPE
из-за записи только в сокеты, на которые он установлен.
другой метод-изменить сокет, чтобы он никогда не создавал SIGPIPE при записи(). Это более удобно в библиотеках, где вам может не понадобиться глобальный обработчик сигналов для SIGPIPE.
на большинстве BSD-систем (MacOS, FreeBSD...) системы, (предполагая, что вы используете C / C++), вы можете сделать это с помощью:
int set = 1; setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
в связи с этим эффектом, а не генерируется сигнал sigpipe, ЭПИПЭ будут возвращены.
Я очень опаздываю на вечеринку, но
SO_NOSIGPIPE
не переносится и может не работать в вашей системе (похоже, это вещь BSD).хорошая альтернатива, если вы находитесь, скажем, в системе Linux без
SO_NOSIGPIPE
было бы установитьMSG_NOSIGNAL
флаг на вашем вызове send (2).пример замены
write(...)
bysend(...,MSG_NOSIGNAL)
(см. nobarкомментарий)char buf[888]; //write( sockfd, buf, sizeof(buf) ); send( sockfd, buf, sizeof(buf), MSG_NOSIGNAL );
в этой post Я описал возможное решение для случая Solaris, когда ни SO_NOSIGPIPE, ни MSG_NOSIGNAL не доступны.
вместо этого мы должны временно подавить SIGPIPE в текущем потоке, который выполняет код библиотеки. Вот как это сделать: чтобы подавить SIGPIPE, мы сначала проверяем, находится ли он в ожидании. Если это так, это означает, что он заблокирован в этом потоке, и мы ничего не должны делать. Если библиотека создает дополнительный сигнал sigpipe, будет слит в ожидании одного, а это нет. Если сигнал sigpipe, не в ожидании, то мы блокируем его в этой теме, а также проверить, есть ли он уже был заблокирован. Тогда мы можем свободно выполнять наши записи. Когда мы должны восстановить SIGPIPE в исходное состояние, мы делаем следующее: если SIGPIPE изначально ожидал, мы ничего не делаем. В противном случае мы проверяем, если он находится в ожидании сейчас. Если это так (что означает, что действия out создали один или несколько Сигпайпов), то мы ждем его в этом потоке, тем самым очищая его состояние ожидания (для этого мы используем sigtimedwait() с нулевым таймаутом; это позволяет избежать блокировки в сценарии, когда вредоносный пользователь отправил SIGPIPE вручную на весь процесс: в этом случае мы увидим его в ожидании, но другой поток может обработать его, прежде чем мы изменим его). После очистки отложенного статуса мы разблокируем SIGPIPE в этом потоке, но только если он не был заблокирован изначально.
пример кода https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156
обрабатывать SIGPIPE локально
обычно лучше всего обрабатывать ошибку локально, а не в глобальном обработчике событий сигнала, поскольку локально у вас будет больше контекста относительно того, что происходит и к чему прибегать.
у меня уровень связи в одном из моих приложений, который позволяет приложение, чтобы взаимодействовать с внешними устройствами. Когда возникает ошибка записи, я бросаю и исключение на уровне связи и позволяю ему пузыриться до блока try catch для его обработки там.
код:
код для игнорирования сигнала SIGPIPE, чтобы вы могли обрабатывать его локально:
// We expect write failures to occur but we want to handle them where // the error occurs rather than in a SIGPIPE handler. signal(SIGPIPE, SIG_IGN);
этот код предотвратит появление сигнала SIGPIPE, но вы получите ошибку чтения / записи при попытке использовать сокет, поэтому вам нужно будет проверить это.
вы не можете предотвратить выход процесса на дальнем конце трубы, и если он выйдет до того, как вы закончите писать, вы получите сигнал SIGPIPE. Если вы SIG_IGN сигнал, то ваша запись вернется с ошибкой - и вам нужно отметить и реагировать на эту ошибку. Просто ловить и игнорировать сигнал в обработчике не очень хорошая идея - вы должны отметить, что канал теперь не функционирует и изменить поведение программы, чтобы она не писала в канал снова (потому что сигнал будет генерируется снова, и игнорируется снова, и вы будете пытаться снова, и весь процесс может продолжаться в течение долго время и тратить много ресурсов процессора).
или я должен просто поймать SIGPIPE с обработчиком и игнорировать его?
Я считаю, что это правильно. Вы хотите знать, когда другой конец закрыл свой дескриптор, и это то, что SIGPIPE говорит вам.
Сэм
руководство Linux сказал:
EPIPE локальный конец был отключен при ориентированном соединении разъем. В этом случае процесс также получит сигнал sigpipe, если в находится.
но для Ubuntu 12.04 это не правильно. Я написал тест для этого случая, и я всегда получаю EPIPE without SIGPIPE. SIGPIPE генерируется, если я пытаюсь написать в тот же сломанный сокет второй раз. Так что вам не нужно игнорировать SIGPIPE, если этот сигнал происходит это означает логическую ошибку в вашей программе.
какова наилучшая практика для предотвращения аварии здесь?
либо отключить sigpipes как для всех, либо поймать и игнорировать ошибку.
есть ли способ проверить, если другая сторона строки все еще читает?
да, используйте select ().
выбрать() не работает здесь, так как он всегда говорит, что сокет доступен для записи.
вам нужно выбрать на читать биты. Вы, вероятно, можете игнорировать написать бит.
когда дальний конец закрывает свой дескриптор файла, select сообщит вам, что есть данные, готовые к чтению. Когда вы идете и читаете это, вы получите обратно 0 байт, и именно так ОС говорит вам, что дескриптор файла был закрыт.
единственный раз, когда вы не можете игнорировать биты записи, - это если вы отправляете большие тома, и есть риск того, что другой конец будет забит, что может привести к заполнению ваших буферов. Если это происходит, а затем попытка записи в дескриптор файла может привести к блокировке или сбою вашей программы/потока. Тестирование select перед написанием защитит вас от этого, но это не гарантирует, что другой конец здоров или что ваши данные будут поступать.
обратите внимание, что вы можете получить сигнал sigpipe, с закрыть(), а также Когда вы пишете.
закрыть сбрасывает все буферизованные данные. Если другой конец уже был закрыт, потом закрыть не удастся, и Вы получит сигнал sigpipe,.
Если вы используете буферизованный TCPIP, то успешная запись просто означает, что ваши данные были поставлены в очередь для отправки, это не значит, что они были отправлены. Пока вы успешно не вызовете close, вы не знаете, что ваши данные были отправлены.
Sigpipe говорит вам, что что-то пошло не так, он не говорит вам, что или что вы должны с этим делать.