Когда неблокирующая send () передает только частичные данные, можем ли мы предположить, что при следующем вызове она вернет EWOULDBLOCK?

На страницах руководства хорошо описаны два случая для неблокирующих сокетов:

  • Если send () возвращает ту же длину, что и буфер передачи, вся передача завершена успешно, и сокет может или не может быть в состоянии возврата EAGAIN / EWOULDBLOCK при следующем вызове с> 0 байтами для перечислить.
  • Если send () возвращает -1, а errno - EAGAIN / EWOULDBLOCK, никакая передача не завершена, и программе необходимо дождаться, пока сокет не будет готов для получения дополнительных данных (EPOLLOUT в случае epoll).

Что не задокументировано для неблокирующих сокетов:

  • Если send () возвращает положительное значение, меньшее размера буфера.

Можно ли предположить, что send () вернет EAGAIN / EWOULDBLOCK даже для еще одного байта данных? Или неблокирующая программа должна попытаться send () еще раз, чтобы получить окончательный EAGAIN / EWOULDBLOCK? Меня беспокоит установка наблюдателя EPOLLOUT в сокет, если он на самом деле не находится в состоянии «заблокировать», чтобы ответить на его выход.

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


person David Timothy Strauss    schedule 15.10.2013    source источник
comment
@Damon, твоя правка полностью изменила смысл вопроса.   -  person user207421    schedule 16.10.2013
comment
@EJP: OP, очевидно, знает о EWOULDBLOCK (или о том, как обычно работают неблокирующие сокеты, по большей части), поэтому, на мой взгляд, можно с уверенностью сказать, что формулировка будет блокировать, которая, казалось, вас смутила, - это просто плохая формулировка, но не то, что задумано.   -  person Damon    schedule 16.10.2013
comment
@Damon Это для меня совсем не очевидно. Ясно, что это именно то, что смутило ОП. Не я. В этом была вся суть. Убрав это из вопроса, вы удалили все его значение. Не делай этого. Если вы хотите ответить на вопрос, обязательно сделайте это. Но не меняйте его просто так, как вам удобно.   -  person user207421    schedule 16.10.2013
comment
Дэймон прав. Я обновил вопрос, чтобы быть точнее. Я знаю, что неблокирующие сокеты никогда не блокируются, просто возвращают это.   -  person David Timothy Strauss    schedule 17.10.2013
comment
Вы не можете ничего предполагать. Драйвер карты nic, вероятно, асинхронный, ваш компьютер, вероятно, асинхронный и т. Д. Буфер отправки может быть опустошен другим ядром, пока ваша отправка находилась в процессе и т. Д.   -  person JimR    schedule 17.10.2013


Ответы (2)


Вызов send имеет три возможных результата:

  1. В буфере отправки имеется по крайней мере один байт send успешно и возвращает количество принятых байтов (возможно, меньше, чем вы запрашивали).
  2. Буфер отправки полностью заполнен на момент вызова send.
    если сокет блокируется, send блокируется
    если сокет не блокируется, send завершается ошибкой с _6 _ / _ 7_
  3. Произошла ошибка (например, пользователь вытащил сетевой кабель, соединение сброшено одноранговым узлом) send не удается с другой ошибкой

Если количество байтов, принятых send, меньше, чем количество, которое вы запросили, то это, следовательно, означает, что буфер отправки теперь полностью заполнен. Однако это чисто косвенный и неавторитарный в отношении любых будущих вызовов send.
Информация, возвращаемая send, является просто «снимком» текущего состояния на момент вызова send. К тому времени, когда send вернется или когда вы снова позвоните send, эта информация может уже быть устаревшей. Сетевая карта может передать дейтаграмму в сеть, когда ваша программа находится внутри send, или через наносекунду, или в любое другое время - нет никакого способа узнать. Вы узнаете, когда следующий вызов будет успешным (а когда нет).

Другими словами, это не означает, что следующий вызов send вернет _17 _ / _ 18_ (или заблокирует, если сокет не был неблокирующим). Пытаться до тех пор, пока то, что вы называете «получение окончательного EWOULDBLOCK», не станет правильным.

person Damon    schedule 16.10.2013
comment
Я предполагаю, что это правильно только для неблокирования: отправка завершается успешно и возвращает количество принятых байтов (возможно, меньше, чем вы просили). Блокирующий сокет должен блокироваться до тех пор, пока он не завершит отправку всех данных или не завершится ошибкой по другим причинам. Я бы либо добавил сценарий блокировки к №1, либо отказался от сценария блокировки с №2 (отвечая исключительно на неблокирующие сокеты). - person David Timothy Strauss; 17.10.2013
comment
@DavidTimothyStrauss: Удивительно, но ваше предположение полностью соответствует формулировкам справочной страницы и POSIX (формулировка в аналогичном системном вызове write явно упоминает частичную запись). И хотя эта формулировка имеет смысл для сокетов датаграмм (которые вы не можете использовать с send!), Она не имеет смысла для сокетов, ориентированных на соединение. Отправка, например, 100 КБ за один раз, безусловно, допустимо, но с учетом типичного размера буфера 64 КБ, если все сообщение должно уместиться, это будет означать, что ваша программа будет блокировать навсегда (потому что она никогда не поместится во все сообщение в буфер). - person Damon; 17.10.2013
comment
Для сокетов дейтаграмм, конечно, имеет смысл, поскольку может быть отправлено только полное сообщение, что полное сообщение должно поместиться в буфере. Но сокеты дейтаграмм должны иметь буфер, который может содержать хотя бы одну дейтаграмму максимального размера. Это означает, что sendto не будет блокировать навсегда. Для потоковых сокетов частичная отправка существует уже 3 десятилетия (независимо от запутанной формулировки на страницах руководства), см., Например, Руководство Beej по этому вопросу (руководство Beej по работе с сетями, как правило, очень полезно для чтения). - person Damon; 17.10.2013
comment
Несколько лет назад было обсуждение этого вопроса в новостях: comp.protocols.tcp-IP, где все согласились, что блокировка send () s блокируется до тех пор, пока все данные не будут помещены в буфер. В эту группу новостей входят все разработчики TCP. Они должны знать. - person user207421; 18.10.2013
comment
@EJP: Это страшная мысль, но что еще страшнее, так это то, что истинное наблюдаемое поведение, например под линуксом пока что совсем другое. В моем компьютере Debian (ядро 3.2) размер буфера отправки по умолчанию равен 84 КБ, и send с радостью принимает буферы размером 64 МБ за один send и возвращает отправленные 64 МБ. Неужто 64МиБ ›84киБ? Исходный код: pastebin.com/T6N7hQky - программа запускает сервер, который потребляет весь трафик на порту 12345, и разветвляется клиент, который записывает увеличивающиеся фрагменты (от 64 КБ до 64 МБ, с шагом 16 КБ) на этот порт, считая "нет". отправлений. - person Damon; 18.10.2013
comment
@Damon Blocking send () гарантирует, что данные были как минимум буферизованы. Он может пройти много циклов буфер-отправка-буфер и вернуться после того, как все 64 МБ будут полностью отправлены или буферизованы. - person David Timothy Strauss; 19.10.2013
comment
@DavidTimothyStrauss: Да, конечно. Но если принять формулировку POSIX (или того, что сказал EJP) буквально, то в любом случае он должен заблокироваться, если полное сообщение размером 64 МБ не помещается в буфер отправки, который всегда и навсегда чехол для такой огромной отправки. Я не проверял, сколько send на самом деле примет без блокировки, но кусок в 65 МБ уже ошеломляет. Невозможно сделать 64 МБ WriteFile под Windows, например (в любом случае, не в нормальных условиях, это будет работать против квоты заблокированных страниц вашего процесса). - person Damon; 19.10.2013
comment
А теперь, пожалуйста, пристегните ремни безопасности ... Я просто изменил приведенный выше код, чтобы попытаться заблокировать отправку с 512 МБ на 1 ГБ, для смеха. Угадайте, что, send тоже принимает посылку размером 1 ГБ в одном блоке. Нет частичной отправки, и из-за того, что процесс занимает 97,4% ЦП во время работы, кажется, что он также не блокируется. - person Damon; 19.10.2013
comment
@Damon: конечно, блокирует. Получатель просто быстро потребляет данные (или у вас может быть огромное окно отправки), немедленно разбудив отправителя. В частности, в Linux / TCP цикл отправки в tcp_sendmsg() вызывает sk_stream_wait_memory()sk_wait_event()schedule_timeout(), когда нет доступного места в памяти / буфере отправки. - person ninjalj; 10.03.2014
comment
@Damon Мы согласны. Все данные буферизуются перед возвратом send (). Если данные, которые должны быть отправлены, больше, чем буфер отправки, очевидно, что буфер отправки должен быть отправлен, чтобы освободить место для большего количества данных, и так постоянно, пока все данные не будут учтены. Затем send () возвращается. - person user207421; 13.09.2014
comment
@Damon BTW send() действительно может использоваться с подключенными сокетами UDP. - person user207421; 19.05.2016

Если send () возвращает ту же длину, что и буфер передачи, вся передача завершена успешно, и сокет может находиться или не находиться в состоянии блокировки.

Нет. Сокет остается в том же режиме, в котором он был: в данном случае это неблокирующий режим, предполагаемый ниже.

Если send () возвращает -1, а errno - EAGAIN / EWOULDBLOCK, передача не завершена, и программе необходимо дождаться, пока сокет перестанет блокироваться.

Пока буфер отправки больше не заполнится. Сокет остается в неблокирующем режиме.

Если send () возвращает положительное значение, меньшее размера буфера.

В буфере отправки сокета было только столько места.

Можно ли предположить, что send () заблокирует еще один байт данных?

Небезопасно вообще «предполагать, что [он] заблокирует». Не будет. Это в неблокирующем режиме. EWOULDBLOCK означает, что он будет заблокирован в режиме блокировки.

Или неблокирующая программа должна попытаться send () еще раз, чтобы получить окончательный EAGAIN / EWOULDBLOCK?

Решать вам. API работает в зависимости от того, что вы решите.

Меня беспокоит установка наблюдателя EPOLLOUT в сокет, если он на самом деле не блокирует это.

Это не «блокировка». Он ничего не блокирует. Это в неблокирующем режиме. В этот момент буфер отправки был заполнен. Через мгновение он может оказаться совершенно пустым.

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

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

person user207421    schedule 15.10.2013