, задавая IP-адрес и номер порта собеседника. Затем оно использует функции
read
и
write
для обмена данными с собеседником.
Дейтаграммы, приходящие с любого другого IP-адреса или порта (который мы обозначаем как «???» на рис. 8.7), не передаются на присоединенный сокет, поскольку либо IP-адрес, либо UDP-порт отправителя не совпадают с адресом протокола, с которым сокет соединяется с помощью функции
connect
. Эти дейтаграммы могут быть доставлены на какой-то другой сокет UDP на узле. Если нет другого совпадающего
сокета для приходящей дейтаграммы, UDP проигнорирует ее и сгенерирует ICMP-сообщение о недоступности порта.
Обобщая вышесказанное, мы можем утверждать, что клиент или сервер UDP может вызвать функцию
connect
, только если этот процесс использует сокет UDP для связи лишь с одним собеседником. Обычно именно клиент UDP вызывает функцию
connect
, но существуют приложения, в которых сервер UDP связывается с одним клиентом на длительное время (например, TFTP), и в этом случае и клиент, и сервер вызывают функцию
connect
.
Еще один пример долгосрочного взаимодействия — это DNS (рис. 8.8).
Рис. 8.8. Пример клиентов и серверов DNS и функции connect
Клиент DNS может быть сконфигурирован для использования одного или более серверов, обычно с помощью перечисления IP-адресов серверов в файле
/etc/resolv.conf
. Если в этом файле указан только один сервер (на рисунке этот клиент изображен в крайнем слева прямоугольнике), клиент может вызвать функцию connect, но если перечислено множество серверов (второй справа прямоугольник на рисунке), клиент не может вызвать функцию
connect
. Обычно сервер DNS обрабатывает также любые клиентские запросы, следовательно, серверы не могут вызывать функцию
connect
.
Многократный вызов функции connect для сокета UDP
Процесс с присоединенным сокетом UDP может снова вызвать функцию
connect
Для этого сокета, чтобы:
задать новый IP-адрес и порт;
отсоединить сокет.
Первый случай, задание нового собеседника для присоединенного сокета UDP, отличается от использования функции
connect
с сокетом TCP: для сокета TCP функция
connect
может быть вызвана только один раз.
Чтобы отсоединить сокет UDP, мы вызываем функцию
connect
, но присваиваем элементу семейства структуры адреса сокета (
sin_family
для IPv4 или
sin6_family
для IPv6) значение
AF_UNSPEC
. Это может привести к ошибке
EAFNOSUPPORT
[128, с. 736], но это нормально. Именно процесс вызова функции
connect
на уже присоединенном сокете UDP позволяет отсоединить сокет [128, с. 787–788].
ПРИМЕЧАНИЕ
В руководстве BSD по поводу функции connect традиционно говорилось: «Сокеты дейтаграмм могут разрывать связь, соединяясь с недействительными адресами, такими как пустые адреса». К сожалению, ни в одном руководстве не сказано, что представляет собой «пустой адрес», и не упоминается, что в результате возвращается ошибка (что нормально). Стандарт POSIX явно указывает, что семейство адресов должно
быть установлено в AF_UNSPEC, но затем сообщает, что этот вызов функции connect может возвратить, а может и не возвратить ошибку EAFNOSUPPORT.
Производительность
Когда приложение вызывает функцию
sendto
на неприсоединенном сокете UDP, ядра реализаций, происходящих от Беркли, временно соединяются с сокетом, отправляют дейтаграмму и затем отсоединяются от сокета [128, с. 762–763]. Таким образом, вызов функции
sendto
для последовательной отправки двух дейтаграмм на неприсоединенном сокете включает следующие шесть шагов, выполняемых ядром:
присоединение сокета;
вывод первой дейтаграммы;
отсоединение сокета;
присоединение сокета;
вывод второй дейтаграммы;
отсоединение сокета.
ПРИМЕЧАНИЕ
Другой момент, который нужно учитывать, — количество поисков в таблице маршрутизации. Первое временное соединение производит поиск в таблице маршрутизации IP-адреса получателя и сохраняет (кэширует) эту информацию. Второе временное соединение отмечает, что адрес получателя совпадает с кэшированным адресом из таблицы маршрутизации (мы считаем, что обеим функциям sendto задан один и тот же получатель), и ему не нужно снова проводить поиск в таблице маршрутизации [128, с. 737–738].
Когда приложение знает, что оно будет отправлять множество дейтаграмм одному и тому же собеседнику, эффективнее будет присоединить сокет явно. Вызов функции
connect
, за которым следуют два вызова функции
write
, теперь будет включать следующие шаги, выполняемые ядром:
присоединение сокета;
вывод первой дейтаграммы;
вывод второй дейтаграммы.
В этом случае ядро копирует структуру адреса сокета, содержащую IP-адрес получателя и порт, только один раз, а при двойном вызове функции
sendto
копирование выполняется дважды. В [89] отмечается, что на временное присоединение отсоединенного сокета UDP приходится примерно треть стоимости каждой передачи UDP.
8.12. Функция dg_cli (продолжение)
Вернемся к функции
dg_cli
, показанной в листинге 8.4, и перепишем ее, с тем чтобы она вызывала функцию
connect
. В листинге 8.7 показана новая функция.
Листинг 8.7. Функция dg_cli, вызывающая функцию connect
//udpcliserv/dgcliconnect.c
1 #include "unp.h"
2 void
3 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)