первой возвращаемой структуры указывает на каноническое имя узла. В терминах DNS это обычно полное доменное имя (FQDN). Программы типа
telnet
широко используют этот флаг для того, чтобы выводить канонические имена систем, к которым производится подключение. Пользователь может указать короткое имя узла или его альтернативное имя, но он должен знать, с какой системой он в результате соединился.
На рис. 11.3 представлена возвращаемая информация для следующего вызова:
struct addrinfo hints, *res;
bzero(&hints, sizeof(hints));
hints.ai_flags = AI_CANONNAME;
hints.ai_family = AF_INET;
getaddrinfo("bsdi", "domain", &hints, &res);
На
этом рисунке все, кроме переменной
res
, относится к динамически выделяемой памяти (например, с помощью функции
malloc
). Предполагается, что каноническое имя узла
freebsd4
—
freebsd4.unpbook.com
, и что этот узел имеет два адреса IPv4 в DNS.
Порт 53 предназначен для службы
domain
, и нужно учитывать, что этот номер порта будет представлен в структурах адресов сокетов в сетевом порядке байтов. Мы приводим возвращаемые значения
ai_protocol
IPPROTO_TCP и IPPROTO_UDP. Функция
getaddrinfo
может возвращать значение
ai_protocol
равное 0 для структур SOCK_STREAM, если этого достаточно для однозначного определения протокола (типа сокета недостаточно, например, если в системе помимо TCP реализован и SCTP), и 0 для структур SOCK_DGRAM, если в системе не реализованы другие протоколы дейтаграмм для IP (на момент написания этой книги стандартизованных протоколов еще не было, но два уже разрабатывались IETF). Лучше всего, если
getaddrinfo
всегда будет возвращать конкретный тип протокола.
В табл. 11.1 показано число структур
addrinfo
для каждого возвращаемого адреса, определяемое на основе заданного имени службы (которое может быть представлено десятичным номером порта) и рекомендации
ai_socktype
.
Таблица 11.1. Число структур addrinfo, возвращаемых для каждого IP-адреса
Элемент ai_socktype
Служба обозначена именем и предоставляется:
Служба обозначена именем порта
Только TCP
Только UDP
Только SCTP
TCP и UDP
TCP и SCTP
TCP, UDP и SCTP
0
1
1
1
2
2
3
Ошибка
SOCK_STREAM
1
Ошибка
1
1
2
2
2
SOCK_DGRAM
Ошибка
1
1
Ошибка
1
1
SOCK_SEQPACKET
Ошибка
Ошибка
1
Ошибка
1
1
1
Более одной структуры
addrinfo
возвращается для каждого IP-адреса только в том случае, когда поле
ai_socktype
структуры
hints
пусто и либо служба поддерживается TCP и UDP (как указано в файле
/etc/services
), либо задан номер порта для этой службы.
Если бы мы рассматривали все 64 возможных варианта сочетаний входных данных для функции
getaddrinfo
(имеется шесть входных переменных), многие сочетания оказались бы недопустимыми, а некоторые не имели бы смысла. Вместо этого рассмотрим наиболее
типичные случаи.
Задание имени узлаи службы. Это традиционный случай для клиента TCP и UDP. По завершении клиент TCP перебирает в цикле все возвращаемые IP-адреса, вызывая функции
socket
и
connect
для каждого из них, пока не установится соединение или пока не будут перебраны все адреса. Мы показываем такой пример с нашей функцией
tcp_connect
в листинге 11.2.
Для клиента UDP структура адреса сокета, заполняемая с помощью функции
getaddrinfo
, будет использоваться в вызове функции
sendto
или
connect
. Если клиент сообщит, что первый адрес не работает (ошибка на присоединенном сокете UDP или тайм-аут на неприсоединенном сокете), будет предпринята попытка обратиться к другому адресу.
Если клиент знает, что он обрабатывает только один тип сокета (например, клиентами Telnet и FTP обрабатываются только сокеты TCP, а клиентами TFTP — только сокеты UDP), то элементу
ai_socktype
структуры
hints
должно быть задано соответственно либо значение
SOCK_STREAM
, либо значение
SOCK_DGRAM
.
Типичный сервер задает службу (service), но не имя узла (hostname), и задает флаг
AI_PASSIVE
в структуре
hints
. Возвращаемая структура адреса сокета должна содержать IP-адрес, равный
INADDR_ANY
(для IPv4) или
IN6ADDR_ANY_INIT
(для IPv6). Сервер TCP затем вызывает функции
socket
,
bind
и
listen
. Если сервер хочет разместить в памяти с помощью функции
malloc
другую структуру адреса сокета, чтобы получить адрес клиента из функции
accept
, то возвращаемое значение
ai_addrlen
задает требуемый для этого размер.
Сервер UDP вызовет функции
socket
,
bind
и затем
recvfrom
. Если сервер хочет разместить в памяти с помощью функции
malloc
другую структуру адреса сокета, чтобы получить адрес клиента из функции
recvfrom
, возвращаемое значение
ai_addrlen
также задает нужный размер.
Как и в случае типичного клиентского кода, если сервер знает, что он обрабатывает только один тип сокета, то элемент
ai_socktype
структуры
hints
должен быть задан либо как
SOCK_STREAM
, либо как
SOCK_DGRAM
. Это позволяет избежать возвращения множества структур, с (возможно) неверным значением элемента
ai_socktype
.
До сих пор мы демонстрировали серверы TCP, создающие один прослушиваемый сокет, и серверы UDP, создающие один сокет дейтаграмм. Это тот вариант, который подразумевался в предыдущем абзаце. Альтернативным устройством является сервер, который обрабатывает множество сокетов с помощью функции
select
. В этом сценарии сервер должен последовательно перебрать все структуры из списка, возвращаемого функцией
getaddrinfo
, создать по одному сокету для каждой структуры и вызвать функцию
select
.
ПРИМЕЧАНИЕ
Проблема этой технологии состоит в том, что условие, по которому функция getaddrinfo возвращает множество структур, возникает, когда служба может обрабатываться как протоколом IPv4, так и протоколом IPv6 (см. табл. 11.3). Но эти два протокола не полностью независимы, как мы увидели в разделе 10.2, то есть если мы создаем прослушиваемый сокет IPv6 для данного порта, нет необходимости создавать для него прослушиваемый сокет IPv4, поскольку соединения, приходящие от клиентов IPv4, автоматически обрабатываются стеком протоколов и прослушиваемым сокетом IPv6, при условии, что параметр сокета IPV6_V6ONLY не установлен.