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

13
задан Robert S. Barnes 7 February 2010 в 18:12
поделиться

4 ответа

+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. Потом встают и идут обедать. telnet по необъяснимым причинам дает сбой. Если бы не было SIGPIPE для отправки, то тикер продолжал бы отправлять котировки бесконечно, никогда не зная, что процесс на другом конце потерпел крах, и напрасно тратил системные ресурсы.

12
ответ дан 1 December 2019 в 20:00
поделиться

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

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

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

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

7
ответ дан 1 December 2019 в 20:00
поделиться

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

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

10
ответ дан 1 December 2019 в 20:00
поделиться

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

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

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

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

Интересно в сторону: проверяя сообщение для SIGPIPE, я обнаружил параметр сокета:

#define SO_NOSIGPIPE 0x1022 /* APPLE: No SIGPIPE on EPIPE */
3
ответ дан 1 December 2019 в 20:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: