Почему предполагается, что при отправке через блокирующий сокет передается меньше запрошенных данных?

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

Например, это простой пример общей схемы:

int send_all(int sock, unsigned char *buffer, int len) {
  int nsent;

  while(len > 0) {
    nsent = send(sock, buffer, len, 0);
    if(nsent == -1) // error
      return -1;

    buffer += nsent;
    len -= nsent;
  }
  return 0; // ok, all data sent
}

Даже в справочной странице BSD упоминается, что

... Если в сокете нет свободного места для сообщений для хранения передаваемого сообщения, то send () обычно блокируется ...

Это означает, что мы должны предположить, что send может вернуться без отправки всех данных. Сейчас я нахожу это довольно неуместным, но даже У. Ричард Стивенс предполагает это в своем стандартном справочнике по сетевому программированию, не в первых главах, но в более сложных примерах вместо вызова write используется его собственная функция Writen (запись всех данных).

Теперь я считаю, что это все еще более или менее нарушено, поскольку, если send не может передать все данные или принять данные в базовом буфере, а сокет блокируется, тогда send должен блокироваться и возвращаться, когда весь запрос отправки был принят. .

Я имею в виду, что в приведенном выше примере кода, что произойдет, если send вернется с меньшим количеством отправленных данных, так это то, что он будет вызываться снова с новым запросом. Что изменилось с момента последнего звонка? Прошло не более нескольких сотен циклов ЦП, поэтому буфер все еще заполнен. Если send now принимает данные, почему он не мог их принять раньше?

В противном случае мы закончим upp неэффективным циклом, в котором мы пытаемся отправить данные в сокет, который не может принимать данные, и продолжаем попытки, иначе?

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


person Ernelli    schedule 11.04.2010    source источник
comment
Это не предполагается. Это может произойти только в режиме прерывания или в неблокирующем режиме.   -  person user207421    schedule 17.01.2020


Ответы (1)


В приведенном выше описании отсутствует то, что в Unix системные вызовы могут прерываться сигналами. Именно по этой причине блокировка send(2) может вернуть короткий счетчик.

person Nikolai Fetissov    schedule 11.04.2010
comment
Разве это не приведет к EINTR? Или EINTR произойдет только в том случае, если вызов будет прерван до передачи каких-либо данных, как описано в manpagez.com / man / 2 / send, и если данные были отправлены, EINTR не выдается, а вместо этого возвращается количество отправленных данных? - person Ernelli; 12.04.2010
comment
EINTR возвращается только в том случае, если данные еще не были переданы (и обработчик сигнала не был установлен с флагом SA_RESTART). - person mark4o; 12.04.2010
comment
Это действительно единственная причина? Означает ли это, что, если я не установлю никаких обработчиков сигналов, цикл отправки будет ненужным? Это немного упростило бы мне жизнь. К сожалению, документации по этому поводу довольно мало ... - person lxgr; 17.01.2012
comment
@lxgr: SO_SNDTIMEO и, возможно, также close() запуск fd в другом потоке может вызвать короткие записи. Кроме того, сторонние библиотеки могут делать странные вещи с сигналами. - person ninjalj; 24.02.2014
comment
Ах! Мне было интересно, почему он заблокирует и не выполнит ваш запрос. Как часто бывают сигналы? - person Pepijn; 10.04.2014
comment
@Pepijn Как насчет того, чтобы пользователь изменял размер терминала во время работы вашей программы? - person user253751; 06.08.2015
comment
@ninjalj И тайм-аут отправки, и закрытие сокета в другом потоке вызовут ошибку, а не короткую запись. - person user207421; 17.01.2020
comment
@ user207421: Системные интерфейсы POSIX 2.10.16 говорят: Опция SO_SNDTIMEO - это опция для установки значения тайм-аута [... snip ...] Если операция отправки заблокирована на это время, она должна вернуться с частичный счетчик или значение ошибки установлено в [EAGAIN] или [EWOULDBLOCK], если данные не были отправлены. Re: закрытие сокета в другом потоке: я полностью ожидаю, что POSIX разрешит короткую запись в такой ситуации, но я могу ' Не могу найти ссылку на POSIX прямо сейчас. - person ninjalj; 19.01.2020