Может ли кто-нибудь дать мне хорошее объяснение поведения «отправить» для неблокирующих сокетов?

Я прочитал документацию по крайней мере 10 раз, а также прочитал около 10 или около того фрагментов кода и полных программ, в которых для отправки данных используются неблокирующие сокеты. Проблема в том, что некоторые уроки предназначены либо для начинающих (Beejs f.i.), либо довольно небрежны в своих предположениях; а те, которые не сложны, представляют собой специализированные примеры кода, которые не объясняют, почему они делают то, что делают. На мой взгляд, даже база знаний SO не полностью охватывает всю гамму поведения send. Что мне нужно, так это подробности о f.e:

  • На что именно указывает код возврата 0, и стоит ли тогда проверять errno или следует просто отказаться от соединения без дальнейшего расследования?
  • Гарантирует ли получение отрицательного возвращаемого значения закрытие испорченного соединения или только так, если только errno не является EWOULDBLOCK, EAGAIN или EINTR (...другие)?
  • Стоит ли проверять errno, когда возвращаемое значение равно > 0? По-видимому, значение указывает на количество «отправленных» данных (в кавычках, потому что это действительно долгий процесс, верно), но, поскольку сокет неблокирующий, означает ли это, что можно сразу же выполнить другой вызов или, в зависимости от errno снова , следует дождаться следующего случая отправки (используя select/poll/epoll)?
  • В принципе, сначала проверяется возвращаемое значение, а затем значение errno? Или, может быть, send устанавливает errno при каждом вызове, независимо от возвращаемого значения? Это сделало бы проверку ошибок несколько проще...
  • Если кто-то получит EINTR, что будет хорошим, надежным поведением для программы? Просто запишите состояние и повторите попытку при следующей отправке, например, с EWOULDBLOCK и EAGAIN?
  • Нужно ли проверять оба EWOULDBLOCK и EAGAIN? Можем ли мы доверять обоим, имеющим одинаковое значение, или это зависит от реализации?
  • send возвращает EMSGSIZE для потоковых сокетов? Если это не так, то размер буфера не слишком велик, верно?
  • Может ли возвращаемое значение быть равным любому из известных кодов ошибок?

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


person amn    schedule 20.03.2011    source источник


Ответы (3)


Тут много вопросов:

  • На что именно указывает код возврата 0, и стоит ли тогда проверять errno или нужно просто отказаться от соединения без дальнейшего расследования?

В системе POSIX send(2) никогда не может вернуть 0, если вы не вызовете его с аргументом длины 0. Проверьте документацию для вашей конкретной системы, чтобы убедиться, что она соответствует спецификации POSIX.

  • Гарантирует ли получение отрицательного возвращаемого значения закрытие соединения, которое испортилось, или это только так, если только errno не является EWOULDBLOCK, EAGAIN или EINTR (...others)?

Нет, возвращаемое значение -1 (единственное возможное отрицательное возвращаемое значение) просто означает, что данные не были отправлены. Вам нужно проверить errno, чтобы понять, ПОЧЕМУ — см. справочную страницу send(2) для получения полного списка всех возможных значений errno и их значения.

  • Стоит ли проверять errno, когда возвращаемое значение > 0? По-видимому, значение указывает на количество «отправленных» данных (в кавычках, потому что это действительно долгий процесс, верно), но, поскольку сокет не блокирует, означает ли это, что можно сразу же выполнить другой вызов или, в зависимости от errno снова , следует дождаться следующего случая отправки (используя select/poll/epoll)?

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

  • В принципе, сначала проверяется возвращаемое значение, а затем значение errno? Или, может быть, send устанавливает errno при каждом вызове, независимо от возвращаемого значения? Это сделало бы проверку ошибок несколько проще...

Сначала проверьте возвращаемое значение, а затем errno, если возвращаемое значение равно -1. Если вы действительно хотите, вы можете установить errno в 0 перед вызовом, а затем проверить его после

  • Если кто-то получит EINTR, что будет хорошим, надежным поведением для программы? Просто запишите состояние и повторите попытку при следующей отправке, например, с EWOULDBLOCK и EAGAIN?

Ну, самое простое — отключить прерывание системных вызовов, и в этом случае вы никогда не получите EINTR. Обращаться с ним так же, как с EWOULDBLOCK/EAGAIN, тоже хорошо.

  • Проверяется ли EWOULDBLOCK и EAGAIN? Можем ли мы доверять обоим, имеющим одинаковое значение, или это зависит от реализации?

Зависит от реализации, хотя обычно они одинаковы. Иногда бывают странности с режимами эмуляции SysV и BSD, которые могут отличать их друг от друга.

  • Отправка возвращает EMSGSIZE для потоковых сокетов? Если это не так, то размер буфера не слишком велик, верно?

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

  • Может ли возвращаемое значение быть равным любому из известных кодов ошибок?

Единственный код ошибки - -1. Успех - это количество записанных байтов, поэтому, если бы вы могли записать 2 ^ 32-1 байт на 32-битной машине (или 2 ^ 64-1 на 64-битной машине), это было бы проблемой, но вы не можете напишите столько байтов (и вы, как правило, получите EINVAL или EFAULT, если попытаетесь).

person Chris Dodd    schedule 20.03.2011
comment
send(2) может возвращать 0, если для len передается ноль. Для протокола дейтаграмм, такого как UDP, это может даже привести к отправке пакета с нулевым байтом. Кроме того, не гарантируется неизменность errno при успешном вызове библиотеки. - person Anomie; 21.03.2011
comment
@Anomie: первая часть достаточно верна, но для второй POSIX гарантирует, что определенные библиотечные вызовы не будут изменять errno, если нет ошибки, и send является одним из этих вызовов. - person Chris Dodd; 21.03.2011
comment
Где ты это видишь? POSIX.1-2008 находится онлайн здесь. На странице errno написано Настройка errno после успешного вызова функция не указана, если только в описании этой функции не указано, что errno не должен изменяться, и страница для отправки, похоже, ничего подобного не говорит. - person Anomie; 21.03.2011
comment
@Anomie - интересно, я имел в виду старую печатную копию POSIX.1, которая у меня есть (датированная 1992 годом). По-видимому, они устранили требование не изменять errno, если в системных вызовах нет ошибок. - person Chris Dodd; 18.05.2011

Я постараюсь ответить на ваши вопросы.

  • Возвращаемое значение 0 из send указывает на то, что было отправлено 0 байтов. На ошибку указывает возвращаемое значение -1. Если вы вызвали send с длиной 0, следует ожидать возврата 0. Хотя неблокирующий сокет должен возвращать -1 с ошибкой EAGAIN или EWOULDBLOCK, если он блокируется, я не слишком удивлюсь, если какая-то реализация вместо этого вернет 0 записанных байтов.
  • EWOULDBLOCK, EAGAIN и EINTR — это ошибки, при которых следует повторить попытку, не закрывайте соединение при получении одной из них. Другие ошибки указывают на проблему, которая, вероятно, должна привести к закрытию.
  • Нет, не проверяйте errno после успешного вызова библиотеки (если только в документации специально не указано, что вы можете сделать это по какой-то причине; я не знаю, чтобы кто-то делал это навскидку). Обратите внимание, что errno может не оставаться неизменным при успешном вызове библиотеки, так как этот вызов мог выполнять другие вызовы, которые возвращали ожидаемые и должным образом обработанные ошибки (например, вызов мог попытаться stat файл, полностью ожидая, что он может не существовать; errno тогда было бы ENOENT, хотя реальной ошибки не было). Если send возвращает короткую запись, вы можете повторить попытку (и, возможно, получить EWOULDBLOCK/EAGAIN), или вы можете дождаться следующего select.
  • Да, сначала проверьте возвращаемое значение. errno не сообщает вам ничего полезного, если вызов прошел успешно.
  • На EINTR вы можете попробовать еще раз немедленно или дождаться следующего раза через цикл select.
  • Вы должны проверить как EAGAIN, так и EWOULDBLOCK; Я полагаю, вы могли бы сделать #if EAGAIN == EWOULDBLOCK, если производительность особенно критична (но помните, профилируйте, а затем оптимизируйте).
  • Все это будет зависеть от базового протокола, но обычно я ожидаю, что протокол потоковой передачи не будет иметь атомарных сообщений (если, возможно, не используется MSG_OOB). Для TCP подойдет любой размер буфера.
  • Конечно, возвращаемое значение может равняться любой из констант errno, но это ничего не значит. Например, в моей системе, если было записано 11 байт, возвращаемое значение будет равно EAGAIN.

ХТН.

person Anomie    schedule 20.03.2011

О EINTR и системных вызовах:

  • если вы используете GLIBC, вам не нужно об этом беспокоиться, по крайней мере, в контексте системных вызовов. Я получил это из часто задаваемых вопросов по Glibc, grep для "Почему не сигнализирует прерывать системные вызовы?"

  • если вы используете LINUX, то вам, вероятно, не нужно беспокоиться о странной семантике системного вызова connect(), которую Дэвид Мадор разглагольствует о здесь. В противном случае будьте готовы к другому, чем обычно, поведению асинхронного вызова connect().

person blacktofu    schedule 03.09.2012