UNIX: разработка сетевых приложений
Рис. 8.8. Пример клиентов и серверов DNS и функции connect
Клиент DNS может быть сконфигурирован для использования одного или более серверов, обычно с помощью перечисления IP-адресов серверов в файле
. Если в этом файле указан только один сервер (на рисунке этот клиент изображен в крайнем слева прямоугольнике), клиент может вызвать функцию connect, но если перечислено множество серверов (второй справа прямоугольник на рисунке), клиент не может вызвать функцию/etc/resolv.conf. Обычно сервер DNS обрабатывает также любые клиентские запросы, следовательно, серверы не могут вызывать функциюconnect.connectМногократный вызов функции connect для сокета UDP
Процесс с присоединенным сокетом UDP может снова вызвать функцию
Для этого сокета, чтобы:connect■ задать новый IP-адрес и порт;
■ отсоединить сокет.
Первый случай, задание нового собеседника для присоединенного сокета UDP, отличается от использования функции
с сокетом TCP: для сокета TCP функцияconnectможет быть вызвана только один раз.connectЧтобы отсоединить сокет UDP, мы вызываем функцию
, но присваиваем элементу семейства структуры адреса сокета (connectдля IPv4 илиsin_familyдля IPv6) значениеsin6_family. Это может привести к ошибкеAF_UNSPEC[128, с. 736], но это нормально. Именно процесс вызова функцииEAFNOSUPPORTна уже присоединенном сокете UDP позволяет отсоединить сокет [128, с. 787–788].connectПРИМЕЧАНИЕВ руководстве BSD по поводу функции connect традиционно говорилось: «Сокеты дейтаграмм могут разрывать связь, соединяясь с недействительными адресами, такими как пустые адреса». К сожалению, ни в одном руководстве не сказано, что представляет собой «пустой адрес», и не упоминается, что в результате возвращается ошибка (что нормально). Стандарт POSIX явно указывает, что семейство адресов должно быть установлено в AF_UNSPEC, но затем сообщает, что этот вызов функции connect может возвратить, а может и не возвратить ошибку EAFNOSUPPORT.
Производительность
Когда приложение вызывает функцию
на неприсоединенном сокете UDP, ядра реализаций, происходящих от Беркли, временно соединяются с сокетом, отправляют дейтаграмму и затем отсоединяются от сокета [128, с. 762–763]. Таким образом, вызов функцииsendtoдля последовательной отправки двух дейтаграмм на неприсоединенном сокете включает следующие шесть шагов, выполняемых ядром:sendto■ присоединение сокета;
■ вывод первой дейтаграммы;
■ отсоединение сокета;
■ присоединение сокета;
■ вывод второй дейтаграммы;
■ отсоединение сокета.
ПРИМЕЧАНИЕДругой момент, который нужно учитывать, — количество поисков в таблице маршрутизации. Первое временное соединение производит поиск в таблице маршрутизации IP-адреса получателя и сохраняет (кэширует) эту информацию. Второе временное соединение отмечает, что адрес получателя совпадает с кэшированным адресом из таблицы маршрутизации (мы считаем, что обеим функциям sendto задан один и тот же получатель), и ему не нужно снова проводить поиск в таблице маршрутизации [128, с. 737–738].
Когда приложение знает, что оно будет отправлять множество дейтаграмм одному и тому же собеседнику, эффективнее будет присоединить сокет явно. Вызов функции
, за которым следуют два вызова функцииconnect, теперь будет включать следующие шаги, выполняемые ядром:write■ присоединение сокета;
■ вывод первой дейтаграммы;
■ вывод второй дейтаграммы.
В этом случае ядро копирует структуру адреса сокета, содержащую IP-адрес получателя и порт, только один раз, а при двойном вызове функции
копирование выполняется дважды. В [89] отмечается, что на временное присоединение отсоединенного сокета UDP приходится примерно треть стоимости каждой передачи UDP.sendto8.12. Функция dg_cli (продолжение)
Вернемся к функции
, показанной в листинге 8.4, и перепишем ее, с тем чтобы она вызывала функциюdg_cli. В листинге 8.7 показана новая функция.connectЛистинг 8.7. Функция dg_cli, вызывающая функцию connect
//udpcliserv/dgcliconnect.c1 #include "unp.h"2 void3 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)4 {5 int n;6 char sendline[MAXLINE], recvline[MAXLINE + 1];7 Connect(sockfd, (SA*)pservaddr, servlen);8 while (Fgets(sendline, MAXLINE, fp) != NULL) {9 Write(sockfd, sendline, strlen(sendline));10 n = Read(sockfd, recvline, MAXLINE);11 recvline[n] = 0; /* завершающий нуль */12 Fputs(recvline, stdout);13 }14 }Изменения по сравнению с предыдущей версией — это добавление вызова функции
и замена вызовов функцийconnectи recvfrom вызовами функцийsendtoиwrite. Функцияreadостается не зависящей от протокола, поскольку она не вникает в структуру адреса сокета, передаваемую функцииdg_cli. Наша функцияconnectклиента, показанная в листинге 8.3, остается той же.mainЕсли мы запустим программу на узле
, задав IP-адрес узлаmacosx(который не запускает наш сервер на порте 9877), мы получим следующий вывод:freebsd4macosx % <b>udpcli04 172.24.37.94</b><b>hello, world</b>read error: Connection refusedПервое, что мы замечаем, — мы не получаем ошибку, когда запускаем процесс клиента. Ошибка происходит только после того, как мы отправляем серверу первую дейтаграмму. Именно отправка этой дейтаграммы вызывает ошибку ICMP от узла сервера. Но когда клиент TCP вызывает функцию
, задавая узел сервера, на котором не запущен процесс сервера, функцияconnectвозвращает ошибку, поскольку вызов функцииconnectвызывает отправку первого пакета трехэтапного рукопожатия TCP, и именно этот пакет вызывает получение сегмента RST от собеседника (см. раздел 4.3).connect