Подтвердить что ты не робот

Может отправить() на TCP-сокет return >= 0 и <length?

Я видел несколько вопросов относительно send(), которые обсуждают базовый протокол. Я полностью понимаю, что для TCP любое сообщение может быть разбито на части по мере его отправки, и нет гарантии, что получатель получит сообщение в одной атомной операции. В этом вопросе я говорю исключительно о поведении системного вызова send(), поскольку он взаимодействует с сетевым уровнем локальной системы.

В соответствии со стандартом POSIX и документацией send(), которую я прочитал, длина отправляемого сообщения определяется аргументом length. Обратите внимание, что: send() отправляет одно сообщение длины длины. Далее:

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

Я не вижу никакой возможности в этом определении для send() когда-либо возвращать любое значение, отличное от -1 (что означает, что никакие данные не помещаются в ядре для передачи) или length, что означает, что все сообщение очереди в ядре, которое должно быть передано. I.e., мне кажется, что send() должен быть атомарным относительно локального очередности сообщения для доставки в ядре.

  • Если в ядре для ядра есть достаточное количество места для всего сообщения, и никакой сигнал не возникает (обычный случай), он копирует и возвращает длину.
  • Если во время send() появляется сигнал, тогда он должен возвращать -1. Очевидно, что в этом случае мы не можем поставить в очередь часть сообщения, так как мы не знаем, сколько было отправлено. Поэтому в этой ситуации ничего не может быть отправлено.
  • Если в очереди на ящик в ядре недостаточно места для всего сообщения, а сокет блокируется, то согласно вышеприведенному утверждению send() должен блокироваться, пока не станет доступным пространство. Затем сообщение будет поставлено в очередь и send() возвращает длину.
  • Если в ядре для ящика нет достаточного количества места для всего сообщения, а сокет не блокируется, то send() должен выйти из строя (return -1), а errno будет установлен на EAGAIN > или EWOULDBLOCK. Опять же, поскольку мы возвращаем -1, ясно, что в этой ситуации никакая часть сообщения не может быть поставлена ​​в очередь.

Я что-то упустил? Возможно ли отправить send() значение >=0 && <length? В какой ситуации? Как насчет систем, отличных от POSIX/UNIX? Соответствует ли реализация Windows send() этому?

4b9b3361

Ответ 1

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

Ответ 2

  • В соответствии со спецификацией Posix и всем человеком, отправляющим страницы, которые я когда-либо видел через 30 лет, да, send() может возвращать любое значение > 0 и <= length. Обратите внимание, что он не может вернуть ноль.

  • Согласно обсуждению несколько лет назад о новостях: comp.protocols.tcp-ip, где все разработчики TCP, блокировка send() фактически не вернется, пока не переведет все данные на socket send buffer: другими словами, возвращаемое значение равно -1 или length.. Было решено, что это верно для всех известных реализаций, а также верно для write(), writev(), sendmsg(), writev(),

Ответ 3

Я знаю, как это работает в Linux, с библиотекой GNU C. В этом случае пункт 4 вашего вопроса будет выглядеть по-другому. Если вы установите флаг O_NONBLOCK для файлового дескриптора, и если невозможно отправить в очередь все сообщение в ядре атомарно, send() возвращает количество фактически отправленных байтов (оно может быть от 1 до длины) и errno установлен на EWOULDBLOCK.

(С дескриптором файла, работающим в режиме блокировки, send() будет блокироваться.)

Ответ 4

Можно send() вернуть value >= 0 && < length. Это может произойти, если буфер отправки имеет меньше места, чем длина сообщения при вызове send(). Аналогично, если размер окна текущего приемника, известный отправителю, меньше длины сообщения, может быть отправлена ​​только часть сообщения. Случайно, я видел, как это происходило в Linux через соединение localhost, когда процесс приема был медленным, чтобы выгрузить данные, которые он получал из своего буфера приема.

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

Также возможно получить возвращаемое значение нуля (опять же, по крайней мере, с некоторыми реализациями), если отправлено сообщение с нулевой длиной.

Этот ответ основан на моем опыте, а также на этом SO ответе.

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

Ответ 5

Чтобы немного разъяснить, где говорится:

блокируется до тех пор, пока не будет доступно пространство.

есть несколько способов пробуждения из этого блока/сна:

  • Достаточно свободного места.
  • Сигнал прерывает текущую операцию блокировки.
  • SO_SNDTIMEO установлен для сокета и истекает время ожидания.
  • Другое, например. гнездо закрывается в другом потоке.

Итак, все заканчивается так:

  • Если в ядре для ядра есть достаточное количество места для всего сообщения, и никакой сигнал не возникает (обычный случай), он копирует и возвращает длину.
  • Если во время send() возникает сигнал, он должен вернуть -1. Очевидно, что в этом случае мы не можем поставить в очередь часть сообщения, так как мы не знаем, сколько было отправлено. Поэтому в этой ситуации ничего не может быть отправлено.
  • Если в очереди на ярусе в ядре недостаточно места для всего сообщения, а сокет блокируется, то в соответствии с вышеприведенным оператором send() должен блокироваться до тех пор, пока пространство не станет доступным. Затем сообщение будет поставлено в очередь и send() вернет длину. Затем send() может быть прерван сигналом, время ожидания отправки может пройти,... вызывая короткую отправку/частичную запись. Разумные реализации возвращают -1 и устанавливают errno на адекватное значение, если ничего не было скопировано в буфер отправки.
  • Если в очереди на ярусах недостаточно ячеек для всего сообщения, а сокет не блокируется, то send() должен выполнить сбой (возврат -1), а errno будет установлен в EAGAIN или EWOULDBLOCK. Опять же, поскольку мы возвращаем -1, ясно, что в этой ситуации никакая часть сообщения не может быть поставлена ​​в очередь.

Ответ 6

В 64-битной системе Linux:

sendto(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4294967296, 0, NULL, 0) = 2147479552

Таким образом, даже пытаясь отправить низкие 4ГБ, цыплята Linux отправляют меньше 2ГБ. Итак, если вы думаете, что попросите его отправить 1 ТБ, и он будет терпеливо сидеть там, продолжайте желать.

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

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

И, наконец, POSIX сообщает, что количество отправленных байтов возвращается, точка. И весь Unix и POSIX, которые его формализуют, построены на концепции короткого чтения/записи, которая позволяет масштабировать реализации систем POSIX от самых маленьких встроенных до суперкомпьютеров с пресловутыми "большими данными". Таким образом, не нужно пытаться читать между строк и находить поблажки для конкретной реализации adhoc, которая у вас есть на руках. Существует еще много реализаций, и пока вы следуете слову стандарта, ваше приложение будет переносимым среди них.