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

Блокирующие сокеты: когда, собственно, "send()" возвращается?

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

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

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

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

Вопросы:

  • Означает ли это, что вызов send() всегда будет немедленно возвращаться, если в буфере передачи ядра есть место?
  • Является ли поведение и производительность вызова send() идентичным для TCP и UDP? Если нет, почему бы и нет?
4b9b3361

Ответ 1

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

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

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

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

EDIT:. С другой стороны, поскольку вы можете отправлять гораздо больше данных за системный вызов с помощью TCP, обычно стоимость на байт может быть намного ниже при использовании TCP. Это можно смягчить с помощью sendmmsg() в последних ядрах Linux.

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

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

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

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

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

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

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

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