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

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

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

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

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

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

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

Ответ 1

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

  • В буфере отправки → send имеется хотя бы один байт; send успешно завершает работу и возвращает количество принятых байтов (возможно, меньше, чем вы просили).
  • Буфер отправки полностью заполнен во время вызова send.
    → если сокет блокируется, send блоки
    → если сокет не блокируется, send завершается с ошибкой EWOULDBLOCK/EAGAIN
  • Произошла ошибка (например, пользовательский сетевой кабель, соединение reset by peer) → send сбой с другой ошибкой

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

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

Ответ 2

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

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

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

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

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

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

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

Это не "безопасно", чтобы "предположить, что [это] будет блокировать" вообще. Это не так. Он работает в неблокирующем режиме. EWOULDBLOCK означает, что он заблокирован в режиме блокировки.

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

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

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

Он не "блокирует это". Он ничего не блокирует. Он работает в неблокирующем режиме. Буфер отправки был заполнен в этот момент. Через минуту он может быть полностью пуст.

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

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