Литмир - Электронная Библиотека
Содержание  
A
A

Рис. 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)

 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
и замена вызовов функций
sendto
и recvfrom вызовами функций
write
и
read
. Функция
dg_cli
остается не зависящей от протокола, поскольку она не вникает в структуру адреса сокета, передаваемую функции
connect
. Наша функция
main
клиента, показанная в листинге 8.3, остается той же.

Если мы запустим программу на узле

macosx
, задав IP-адрес узла
freebsd4
(который не запускает наш сервер на порте 9877), мы получим следующий вывод:

macosx % <b>udpcli04 172.24.37.94</b>

<b>hello, world</b>

read error: Connection refused

Первое, что мы замечаем, — мы не получаем ошибку, когда запускаем процесс клиента. Ошибка происходит только после того, как мы отправляем серверу первую дейтаграмму. Именно отправка этой дейтаграммы вызывает ошибку ICMP от узла сервера. Но когда клиент TCP вызывает функцию

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

102
{"b":"225366","o":1}