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