Почему запись закрытого сокета TCP хуже чтения? - PullRequest
13 голосов
/ 07 февраля 2010

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

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

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

Ответы [ 4 ]

12 голосов
/ 08 февраля 2010

+ 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. Кто-то связывается с портом 8000 и набирает «start java», чтобы получить кавычки для Sun Microsystems. Затем они встают и идут на обед. Телнет необъяснимым образом вылетает. Если бы не было SIGPIPE для отправки, то ticker продолжал бы отправлять кавычки навсегда, никогда не зная, что процесс на другом конце завершился сбоем, и без необходимости тратить системные ресурсы.

10 голосов
/ 07 февраля 2010

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

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

7 голосов
/ 07 февраля 2010

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

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

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

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

3 голосов
/ 07 февраля 2010

Я думаю, что большая часть ответа такова: «Так что сокет ведет себя почти так же, как классический Unix (анонимный) канал». Те же демонстрируют такое же поведение - свидетельство о названии сигнала.

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

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

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

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

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