Блокировка сокетов: когда именно "send ()" возвращает? - PullRequest
38 голосов
/ 23 марта 2011

Когда, в точности, функция BSD сокета send() возвращает вызывающему абоненту?

В неблокирующем режиме , она должна немедленно вернуться, правильно?

Что касается режима блокировки , то на справочной странице говорится:

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

Вопросы:

  1. Означает ли это, что send() вызов всегда будет возвращаться немедленно, если в буфере отправки ядра есть место?
  2. Поведение и производительность вызова send() идентичны для TCP и UDP?Если нет, то почему?

Ответы [ 5 ]

33 голосов
/ 01 апреля 2011

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

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

поведение и производительность вызова send () идентичны для TCP и UDP?Если нет, то почему?

Не совсем.Возможные различия в производительности зависят от реализации ОС стека TCP / IP.Теоретически сокет UDP может быть немного дешевле, так как ОС должна делать с ним меньше вещей.

РЕДАКТИРОВАТЬ: С другой стороны, поскольку вы можете отправлять гораздо больше данных за системный вызовс TCP, как правило, стоимость за байт может быть намного ниже с TCP.Это может быть смягчено с помощью sendmmsg () в последних ядрах Linux.

Что касается поведения, оно почти идентично.

Для блокирования сокетов будут блокироваться как TCP, так и UDP.пока не будет места в буфере ядра.Различие, однако, состоит в том, что сокет UDP будет ожидать, пока весь ваш буфер может быть сохранен в буфере ядра, тогда как сокет TCP может решить скопировать только один байт в буфер ядра (хотя обычно это более одного байта).

Если вы попытаетесь отправить пакеты, размер которых превышает 64 КБ, сокет UDP, скорее всего, будет постоянно терпеть неудачу с EMSGSIZE .Это связано с тем, что UDP, являющийся сокетом дейтаграммы , гарантирует отправку всего буфера в виде одного IP-пакета (или последовательности фрагментов IP-пакетов) или не отправляет его вообще.

Non blockingсокеты ведут себя идентично версиям блокировки, за исключением того, что вместо блокировки (если в буфере ядра недостаточно места) вызовы завершаются с EAGAIN (или EWOULDBLOCK ).Когда это происходит, пришло время поместить сокет обратно в epoll / kqueue / select (или что вы используете), чтобы дождаться, когда он снова станет доступным для записи.

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

5 голосов
/ 25 марта 2011

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

1 голос
/ 02 июля 2013

Функция send () вернется, как только данные будут приняты ядром. В случае блокировки сокета: send () будет блокироваться, если буфер ядра недостаточно свободен для приема данных, предоставленных для вызова send ().

Неблокирующие сокеты: send () не будет блокироваться, но завершится с ошибкой и вернет -1 или может вернуть количество скопированных байтов частично (в зависимости от доступного пространства буфера). Это устанавливает errno EWOULDBLOCK или EAGAIN. Это означает, что в то время, когда send (), буфер не смог принять все байты, и вы должны повторить попытку с вызовом select () для отправки () данных снова. Или вы можете поставить цикл с помощью sleep () и call send (), но вам нужно позаботиться о количестве фактически отправленных байтов и об оставшемся количестве байтов, которые должны быть отправлены.

0 голосов
/ 24 марта 2011

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

0 голосов
/ 23 марта 2011

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

Разве не так? Момент, после которого данные «отправляются», можно определить по-разному. Я думаю, что это тот момент, когда ОС приняла ваши данные для доставки в стек. В противном случае это довольно сложно определить. Это момент, когда данные передаются в буфер сетевой карты? Или после того момента, когда данные выталкиваются из буфера сетевой карты?

Есть ли какая-то проблема, которую вам нужно знать наверняка, или вам просто любопытно?

...