Каков будет результат если на udp сокет вызвать listen accept connect

Обновлено: 06.07.2024

Тяжкая работа лёгких протоколов


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

Для примера разберём ситуацию с получением ответа, когда UDP датаграмма с начальным запросом посылается на дополнительный IP адрес на интерфейсе (alias или secondary IP).
Есть интерфейс eth1:


Как обычно выглядит код для получения пакета по udp? Ну, echo сервер может выглядеть как-то очень похоже на то, что под катом:

Это достаточно простой скрипт на перле, который покажет от кого пришёл udp пакет, содержимое пакета и отправит этот пакет обратно отправителю. Проще некуда. Теперь запустим наш сервер:

Посмотрим, что он слушает:

И после этого подключимся с удалённой машины к нашему серверу по основному IP:

Как это выглядит в tcpdump'е на нашей машине (ну, или должно выглядеть):

Просто фантастика — я отправляю пакет и получаю пакет обратно. В netcat'е мы получаем обратно что бы мы не напечатали (смешной эффект, если печатать «стрелочки»).

А теперь тоже самое на вторичный адрес на том же интерфейсе:

Как это сумасшествие выглядит в tcpdump'е на этот раз:


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

Это очень круто и решает проблему. Исключение составляет ситуация, когда вы добавляете лишний алиас уже после того, как демон стартовал. Bind не подцепит новый адрес и вам придётся рестартовать сервер. Кроме того, это несколько усложняет код, так как вам приходится как ладить с кучей сокетов внутри программы (например, использовать select() вместо простого блокирования на попытке приёма.) В общем-то, никто не любит лишних сложностей, но с этой можно справится. Однако, настоящая проблема это правило «не добавляйте адресов после старта демона». Необходимость проверять, не добавилось ли в системе ip-адресов, и рестартовать службу после добавляения адреса станет настоящей проблемой.
Однако, есть некоторый workaround и для этой проблемы. Здесь мы вспомним про ntpd. Порты, которые слушает он, выглядят следующим образом:

На текущий момент, лучшим решением кажется следовать пути Bind'а и ntpd и слушать на всех адреса в отдельности с «фокусом» от ntpd: слушать дополнительно и на 0.0.0.0. При этом, если я получил пакет на 0.0.0.0, то надо запускать сканирования доступных в системе адресов и биндится дополнительно и на них. Это должно решить проблему.
Осталось только заставить это работать (и решить кучу проблем, которые наверняка вылезут на пути). Пожелайте мне удачи. Крики боли и мучений которые вы слышите (не имеет значение, где вы находитесь) наверняка мои.

UPD: в комментариях появилось интересное пояснение от Quasar_ru. Всё таки реализация UDP в скриптовых языках неоднозначна: на чистом С можно написать такое клиентское приложение, которое сможет принять ответ от сервера с другого адреса. Польза от такой реализации — спорная, но всё же реализация возможна.

В конце разделе 8.9 мы упомянули, что асинхронные ошибки не возвращаются на сокете UDP, если сокет не был присоединен. На самом деле мы можем вызвать функцию connect для сокета UDP (см. раздел 4.3). Но это не приведет ни к чему похожему на соединение TCP: здесь не существует трехэтапного рукопожатия. Ядро просто проверяет, нет ли сведений о заведомой недоступности адресата, после чего записывает IP-адрес и номер порта собеседника, которые содержатся в структуре адреса сокета, передаваемой функции connect, и немедленно возвращает управление вызывающему процессу.

Перегрузка функции connect этой новой возможностью для сокетов UDP может внести путаницу. Если используется соглашение о том, что sockname — это адрес локального протокола, a peername — адрес удаленного протокола, то лучше бы эта функция называлась setpeername. Аналогично, функции bind больше подошло бы название setsockname.

С учетом этого необходимо понимать разницу между двумя видами сокетов UDP.

? Неприсоединенный (unconnected) сокет UDP — это сокет UDP, создаваемый по умолчанию.

? Присоединенный — результат вызова функции connect для сокета UDP.

Присоединенному сокету UDP свойственны три отличия от неприсоединенного сокета, который создается по умолчанию.

1. Мы больше не можем задавать IP-адрес получателя и порт для операции вывода. То есть мы используем вместо функции sendto функцию write или send. Все, что записывается в присоединенный сокет UDP, автоматически отправляется на адрес (например, IP-адрес и порт), заданный функцией connect.

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

2. Вместо функции recvfrom мы используем функцию read или recv. Единственные дейтаграммы, возвращаемые ядром для операции ввода через присоединенный сокет UDP, — это дейтаграммы, приходящие с адреса, заданного в функции connect. Дейтаграммы, предназначенные для адреса локального протокола присоединенного сокета UDP (например, IP-адрес и порт), но приходящие с адреса протокола, отличного от того, к которому сокет был присоединен с помощью функции connect, не передаются присоединенному сокету. Это ограничивает присоединенный сокет UDP, позволяя ему обмениваться дейтаграммами с одним и только одним собеседником.

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

3. Асинхронные ошибки возвращаются процессу только при операциях с присоединенным сокетом UDP. В результате, как мы уже говорили, неприсоединенный сокет UDP не получает никаких асинхронных ошибок.

В табл. 8.2 сводятся воедино свойства, перечисленные в первом пункте, применительно к 4.4BSD.

Таблица 8.2. Сокеты TCP и UDP: может ли быть задан адрес протокола получателя

Тип сокета write или send sendto, без указания получателя sendto, с указанием получателя Сокет TCP Да Да EISCONN Сокет UDP, присоединенный Да Да EISCONN Сокет UDP, неприсоединенный EDESTADDRREQ EDESTADDRREQ Да

POSIX определяет, что операция вывода, не задающая адрес получателя на неприсоединенном сокете UDP, должна возвращать ошибку ENOTCONN, а не EDESTADDRREQ.

Solaris 2.5 допускает функцию sendto, которая задает адрес получателя для присоединенного сокета UDP. POSIX определяет, что в такой ситуации должна возвращаться ошибка EISCONN.

На рис. 8.7 обобщается информация о присоединенном сокете UDP.


Рис. 8.7. Присоединенный сокет UDP

Приложение вызывает функцию connect, задавая IP-адрес и номер порта собеседника. Затем оно использует функции read и write для обмена данными с собеседником.

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

Еще один пример долгосрочного взаимодействия — это DNS (рис. 8.8).


Рис. 8.8. Пример клиентов и серверов DNS и функции connect

Клиент DNS может быть сконфигурирован для использования одного или более серверов, обычно с помощью перечисления IP-адресов серверов в файле /etc/resolv.conf. Если в этом файле указан только один сервер (на рисунке этот клиент изображен в крайнем слева прямоугольнике), клиент может вызвать функцию connect, но если перечислено множество серверов (второй справа прямоугольник на рисунке), клиент не может вызвать функцию connect. Обычно сервер DNS обрабатывает также любые клиентские запросы, следовательно, серверы не могут вызывать функцию connect.

Данный текст является ознакомительным фрагментом.

Продолжение на ЛитРес

8.11. Функция connect для UDP

8.11. Функция connect для UDP В конце разделе 8.9 мы упомянули, что асинхронные ошибки не возвращаются на сокете UDP, если сокет не был присоединен. На самом деле мы можем вызвать функцию connect для сокета UDP (см. раздел 4.3). Но это не приведет ни к чему похожему на соединение TCP: здесь не

Многократный вызов функции connect для сокета UDP

Многократный вызов функции connect для сокета UDP Процесс с присоединенным сокетом UDP может снова вызвать функцию connect Для этого сокета, чтобы:? задать новый IP-адрес и порт;? отсоединить сокет.Первый случай, задание нового собеседника для присоединенного сокета UDP, отличается

Тайм-аут для функции connect (сигнал SIGALRM)

Тайм-аут для функции connect (сигнал SIGALRM) В листинге 14.1[1] показана наша функция connect_timeo, вызывающая функцию connect с ограничением по времени, заданным вызывающим процессом. Первые три аргумента — это аргументы, которых требует функция connect, а четвертый — это длительность

16.3. Неблокируемая функция connect

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

16.4. Неблокируемая функция connect: клиент времени и даты

16.4. Неблокируемая функция connect: клиент времени и даты В листинге 16.7 показана наша функция connect_nonb, вызывающая неблокируемую функцию connect. Мы заменяем вызов функции connect, имеющийся в листинге 1.1, следующим фрагментом кода:if (connect_nonb(sockfd, (SA*)&servaddr, sizeof(servaddr), 0) < 0)err_sys("connect

Прерванная функция connect

Прерванная функция connect Что происходит, если наш вызов функции connect на обычном блокируемом сокете прерывается, скажем, перехваченным сигналом, прежде чем завершится трехэтапное рукопожатие TCP? Если предположить, что функция connect не перезапускается автоматически, то она

Структура сети Direct Connect

Структура сети Direct Connect Как уже упоминалось ранее, сеть Direct Connect (Прямое соединение) — это децентрализованная сеть, состоящая из отдельных серверов-коммутаторов (хабов), к которым подключаются компьютеры пользователей для обмена файлами. Пользователи могут искать и

Функция SUM

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

Функция uni()

Функция uni() Поиск/замена символа по его юникодному номеру также может быть сделана при помощи функции uni().Пример функции uni(): Boouni(107,32)Designer найдет слово Book

Функция uni()

Функция uni() Поиск/замена символа по его юникодному номеру также может быть сделана при помощи функции uni().Пример функции uni(): Boouni(107,32)Designer найдет слово Book

Голубятня: Globusbook 950 Connect Сергей Голубицкий

Во-вторых, разница между отправкой и получением данных TCP и UDP.

1. tcp: send отправляет данные, recv принимает данные.

2. udp: sendto отправляет данные, recvfrom получает данные.

2. Разница между send и sendto

TCP основан на потоке данных, а UDP основан на дейтаграмме:

3. Разница между recv и recvfrom

(2)Просто клиент протокола tcp отправляет нулевые данные, которые на самом деле являются нулевыми данными, даже если у клиента бесконечное количество отправляемых нулей, это будет то же самое, что и отсутствие.

(3) TCP основан на канале связи.

Проверка (1): клиент отправляет пустой

Проверка (2): клиент завершает программу напрямую

(2) Просто у клиента sendinto протокола udp есть пустые данные, которые на самом деле не являются пустыми данными (включая: пустые данные + информация об адресе, итоговый отчет все равно не будет пустым), поэтому клиенту нужно только иметь sendinto (независимо от того, отправлено оно или нет). Нулевые данные на самом деле не являются пустыми данными), сервер может восстановить данные.

(3) udp не имеет ссылки

Проверка (1): клиент отправляет пустой, смотрите результат на сервере

Проверка (2): запустить сервер отдельно

Примечание:

2. В приведенной выше программе udp, если вы прокомментируете любую отправленную информацию на клиенте, сервер зависнет.Почему? Потому что несколько recvfrom на стороне сервера должны соответствовать нескольким sendinto, даже sendinto (b '').

Три, липкий пакет и раствор

1. Клейкая сумка

Примечание: только TCP имеет явление залипания пакета, UDP никогда не будет залипать пакет.

Recvfrom udp блокируется, recvfrom (x) должен быть отправлен в (y) один за другим, и x байтов данных принимается, даже если он завершен. Если y> x данные потеряны, это означает, что udp не будет придерживаться к пакету вообще. Но он потеряет данные и ненадежен

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

Липкие пакеты возникают в двух ситуациях:

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

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

Возникновение распаковки

Когда длина буфера отправителя превышает MTU сетевой карты, tcp разделит данные, отправленные на этот раз, на несколько пакетов и отправит их.

Дополнительный вопрос 1: почему TCP является надежной передачей, а UDP - ненадежной передачей

Пока udp отправляет данные, противоположный конец не возвращает подтверждающую информацию, поэтому это ненадежно.

Дополнительный вопрос 2: send (поток байтов) и recv (1024) и sendall

1024, указанное в recv, означает извлечение 1024 байтов данных из кеша за раз.

Посылаемый поток байтов сначала помещается в его собственный буфер, а затем содержимое буфера отправляется на противоположный конец под контролем протокола. Если размер передаваемого потока байтов больше, чем оставшееся пространство буфер, то данные будут потеряны. Sendall вызовет send в цикле. Никакие данные не будут потеряны

2. Решение

Метод первый (младшая версия):

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

Младшая версия решения

Скорость работы программы намного выше, чем скорость передачи по сети, поэтому перед отправкой байта сначала отправьте длину потока байтов.Этот метод усилит потерю производительности, вызванную задержкой в ​​сети.

Метод второй:

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

структурный модуль

Модуль может преобразовывать тип, например число, в байты фиксированной длины.


Мы можем превратить заголовок в словарь, словарь содержит подробную информацию о реальных данных, которые должны быть отправлены, а затем сериализовать его с помощью json, а затем использовать strike, чтобы упаковать длину сериализованных данных в 4 байта (4 достаточно для самих себя)

При отправке:

Длина предварительно отправленного заголовка

Перекодируйте содержимое заголовка и отправьте

Наконец, опубликуйте реальный контент

При получении:

Изначально длина заголовка, используйте структуру, чтобы вынуть его

Соберите содержимое заголовка в соответствии с извлеченной длиной, затем декодируйте и десериализуйте

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

Сокеты (англ. socket — разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.

Принципы сокетов¶

Каждый процесс может создать слушающий сокет (серверный сокет) и привязать его к какому-нибудь порту операционной системы (в UNIX непривилегированные процессы не могут использовать порты меньше 1024). Слушающий процесс обычно находится в цикле ожидания, то есть просыпается при появлении нового соединения. При этом сохраняется возможность проверить наличие соединений на данный момент, установить тайм-аут для операции и т.д.

Каждый сокет имеет свой адрес. ОС семейства UNIX могут поддерживать много типов адресов, но обязательными являются INET-адрес и UNIX-адрес. Если привязать сокет к UNIX-адресу, то будет создан специальный файл (файл сокета) по заданному пути, через который смогут сообщаться любые локальные процессы путём чтения/записи из него (см. Доменный сокет Unix). Сокеты типа INET доступны из сети и требуют выделения номера порта.

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

Основные функции¶

socket()¶

Создаёт конечную точку соединения и возвращает файловый дескриптор. Принимает три аргумента:

domain указывающий семейство протоколов создаваемого сокета

  • AF_INET для сетевого протокола IPv4
  • AF_INET6 для IPv6
  • AF_UNIX для локальных сокетов (используя файл)

type

  • SOCK_STREAM (надёжная потокоориентированная служба (сервис) или потоковый сокет)
  • SOCK_DGRAM (служба датаграмм или датаграммный сокет)
  • SOCK_RAW (Сырой сокет — сырой протокол поверх сетевого уровня).

protocol

Протоколы обозначаются символьными константами с префиксом IPPROTO_* (например, IPPROTO_TCP или IPPROTO_UDP). Допускается значение protocol=0 (протокол не указан), в этом случае используется значение по умолчанию для данного вида соединений.

Функция возвращает −1 в случае ошибки. Иначе, она возвращает целое число, представляющее присвоенный дескриптор.

Пример на Python

Связывает сокет с конкретным адресом. Когда сокет создается при помощи socket(), он ассоциируется с некоторым семейством адресов, но не с конкретным адресом. До того как сокет сможет принять входящие соединения, он должен быть связан с адресом. bind() принимает три аргумента:

  1. sockfd — дескриптор, представляющий сокет при привязке
  2. serv_addr — указатель на структуру sockaddr, представляющую адрес, к которому привязываем.
  3. addrlen — поле socklen_t, представляющее длину структуры sockaddr.

Возвращает 0 при успехе и −1 при возникновении ошибки.

Пример на Python

Автоматическое получение имени хоста.

listen()¶

Подготавливает привязываемый сокет к принятию входящих соединений. Данная функция применима только к типам сокетов SOCK_STREAM и SOCK_SEQPACKET. Принимает два аргумента:

  1. sockfd — корректный дескриптор сокета.
  2. backlog — целое число, означающее число установленных соединений, которые могут быть обработаны в любой момент времени. Операционная система обычно ставит его равным максимальному значению.

После принятия соединения оно выводится из очереди. В случае успеха возвращается 0, в случае возникновения ошибки возвращается −1.

Пример на Python

accept()¶

Используется для принятия запроса на установление соединения от удаленного хоста. Принимает следующие аргументы:

  1. sockfd — дескриптор слушающего сокета на принятие соединения.
  2. cliaddr — указатель на структуру sockaddr, для принятия информации об адресе клиента.
  3. addrlen — указатель на socklen_t, определяющее размер структуры, содержащей клиентский адрес и переданной в accept(). Когда accept() возвращает некоторое значение, socklen_t указывает сколько байт структуры cliaddr использовано в данный момент.

Функция возвращает дескриптор сокета, связанный с принятым соединением, или −1 в случае возникновения ошибки.

Пример на Python

connect()¶

Устанавливает соединение с сервером.

Некоторые типы сокетов работают без установления соединения, это в основном касается UDP-сокетов. Для них соединение приобретает особое значение: цель по умолчанию для посылки и получения данных присваивается переданному адресу, позволяя использовать такие функции как send() и recv() на сокетах без установления соединения.

Загруженный сервер может отвергнуть попытку соединения, поэтому в некоторых видах программ необходимо предусмотреть повторные попытки соединения.

Возвращает целое число, представляющее код ошибки: 0 означает успешное выполнение, а −1 свидетельствует об ошибке.

Пример на Python

Передача данных¶

Для передачи данных можно пользоваться стандартными функциями чтения/записи файлов read и write, но есть специальные функции для передачи данных через сокеты:

Читайте также: