Когда неблокирующая передача() передает только частичные данные, можем ли мы предположить, что она вернет 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 в сокет, если он на самом деле не блокирует это.
Он не "блокирует это". Он ничего не блокирует. Он работает в неблокирующем режиме. Буфер отправки был заполнен в этот момент. Через минуту он может быть полностью пуст.
Я не понимаю, о чем вы беспокоитесь. Если у вас есть ожидающие данные, и последняя запись не отправила все это, выберите для записи и напишите, когда вы его получите. Если такая запись отправляет все, не выбирайте для записи в следующий раз.
Сокеты обычно доступны для записи, если только их буфер отправки не заполнен, поэтому не выбирайте для записи все время, так как вы просто получаете спиновый цикл.