Подтвердить что ты не робот

Почему писать закрытый TCP-разъем хуже, чем читать?

Когда вы читаете закрытый TCP-сокет, вы получаете регулярную ошибку, то есть он либо возвращает 0, указывающий EOF или -1, и код ошибки в errno, который можно распечатать с помощью perror.

Однако, когда вы пишете закрытый TCP-сокет, OS отправляет SIGPIPE в ваше приложение, которое прекратит приложение, если оно не будет обнаружено.

Почему запись закрытого сокета TCP хуже, чем чтение?

4b9b3361

Ответ 1

+1 В Грег Хьюджилл за то, что он вел мой мыслительный процесс в правильном направлении, чтобы найти ответ.

Настоящая причина для SIGPIPE в обоих сокетах и ​​каналах - это идиома/шаблон фильтра, который применяется к типичным ввода-выводам в Unix-системах.

Начиная с труб. Фильтрующие программы, такие как grep, обычно пишут в STDOUT и читают из STDIN, которые могут быть перенаправлены оболочкой в ​​канал. Например:

cat someVeryBigFile | grep foo | doSomeThingErrorProne

Оболочка, когда она вилки и затем выполняет эти программы, вероятно, использует системный вызов dup2 для перенаправления STDIN, STDOUT и STDERR к соответствующим трубам.

Так как программа фильтра grep не знает и не знает, что выход был перенаправлен, тогда единственный способ сказать ему прекратить запись в сломанный канал, если doSomeThingErrorProne сбой с сигналом, так как возвращаемые значения записи в STDOUT редко проверяются.

Аналогом с сокетами будет inetd сервер, заменяющий оболочку.

В качестве примера я предполагаю, что вы можете превратить grep в сетевую службу, которая работает через сокеты TCP. Например, если inetd, если вы хотите иметь сервер grep на TCP порт 8000, добавьте его в /etc/services:

grep     8000/tcp   # grep server

Затем добавьте это в /etc/inetd.conf:

grep  stream tcp nowait root /usr/bin/grep grep foo

Отправьте SIGHUP в inetd и подключитесь к порту 8000 с помощью telnet. Это должно привести к тому, что inetd будет вилка, опустите сокет на STDIN, STDOUT и STDERR, а затем exec grep с foo в качестве аргумента. Если вы начнете вводить строки в telnet, grep будет отображать те строки, которые содержат foo.

Теперь замените telnet на программу с именем ticker, которая, например, записывает поток котировок акций реального времени в STDOUT и получает команды на STDIN. Кто-то telnets к порту 8000 и типы "запускают java" для получения котировок для Sun Microsystems. Затем они встают и идут на обед. telnet необъяснимо сбой. Если бы не было SIGPIPE для отправки, то ticker продолжал бы отправлять кавычки навсегда, никогда не зная, что процесс на другом конце разбился и бесполезно растрачивает системные ресурсы.

Ответ 2

Обычно, если вы пишете в сокет, вы ожидаете, что другой конец будет прослушиваться. Это похоже на телефонный звонок - если вы говорите, вы не ожидаете, что другая сторона просто повесит трубку.

Если вы читаете из сокета, вы ожидаете, что на другом конце либо (a) отправит вам что-нибудь, либо (b) закройте сокет. Ситуация (b) произойдет, если вы только что отправили что-то вроде команды QUIT на другой конец.

Ответ 3

Подумайте о сокетах как о большом конвейере данных между процессом отправки и получения. Теперь представьте, что в трубопроводе закрыт клапан (соединение сокета закрыто).

Если вы читаете сокет (пытаясь получить что-то из трубы), нет никакого вреда в попытке прочитать что-то, чего там нет; вы просто не получите никаких данных. Фактически, вы можете, как вы сказали, получить EOF, что является правильным, так как нет больше данных для чтения.

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

Вы всегда можете игнорировать или блокировать сигнал, но вы делаете это на свой страх и риск.

Ответ 4

Я думаю, что большая часть ответа "так, что сокет ведет себя аналогично классическому Unix (анонимному) трубу". Они также демонстрируют одно и то же поведение - свидетельствуют о названии сигнала.

Итак, тогда разумно спросить, почему трубы ведут себя таким образом. Ответ Грега Хьюджилла дает краткий обзор ситуации.

Еще один способ взглянуть на это - что альтернатива? Должна ли "читать()" на трубе без записи дать сигнал SIGPIPE? Смысл SIGPIPE должен был бы меняться от "писать на трубе, чтобы никто не читал", конечно, но это тривиально. Нет никаких оснований думать, что было бы лучше; индикация EOF (чтение нулевых байтов, чтение с нулевыми байтами) является идеальным описанием состояния канала, и поэтому поведение чтения является хорошим.

Как насчет 'write()'? Ну, вариант должен был бы вернуть количество байтов, записанных - ноль. Но это не очень хорошая идея; это означает, что код должен повторить попытку, и, возможно, будет отправлено больше байтов, чего не будет. Другим вариантом будет ошибка - write() возвращает -1 и устанавливает соответствующее значение errno. Непонятно, что есть одно. EINVAL или EBADF являются неточными: дескриптор файла является правильным и открытым с этой целью (и должен быть закрыт после неудачной записи); просто нечего читать. EPIPE означает "сломанный PIPE"; поэтому, с предостережением о том, что "это сокет, а не труба", это была бы соответствующая ошибка. Вероятно, это ошибка возвращается, если вы игнорируете SIGPIPE. Это было бы возможно сделать - просто верните соответствующую ошибку, когда труба сломана (и никогда не посылает сигнал). Тем не менее, это эмпирический факт, что многие программы не уделяют столько внимания тому, куда идет их выход, и если вы передаете команду, которая будет читать файл с несколькими гигабайтами в процесс, который завершает работу после первых 20 КБ, но он не обращает внимания на статус своих записей, тогда это займет много времени, и при этом будет тратить усилия машины, тогда как, отправив ему сигнал о том, что он не игнорирует, он быстро остановится - это безусловно, выгодно. И вы можете получить ошибку, если хотите. Таким образом, передача сигнала имеет преимущества для o/s в контексте труб; и сокеты эмулируют трубы довольно близко.

Интересно: при проверке сообщения для SIGPIPE я нашел опцию сокета:

#define SO_NOSIGPIPE 0x1022 /* APPLE: No SIGPIPE on EPIPE */