Данные о хостах как узлах сети хранятся в сетевой базе, последовательный доступ к которой обслуживается функциями sethostent(), gethostent() и endhostent() (см. пример 11.1).
Листинг 11.1. Описание функций последовательного доступа к сетевой базе данных о хостах - узлах сети.
Функция sethostent() устанавливает соединение с базой, остающееся открытым после вызова gethostent(), если значение аргумента stayopen отлично от нуля. Функция gethostent() последовательно читает элементы базы, возвращая результат в структуре типа hostent, содержащей по крайней мере следующие поля.
char *h_name; /* Официальное имя хоста */char **h_aliases; /* Массив указателей на альтернативные *//* имена хоста, завершаемый пустым *//* указателем */int h_addrtype; /* Тип адреса хоста */int h_length; /* Длина в байтах адреса данного типа */char **h_addr_list; /* Массив указателей на сетевые адреса *//* хоста, завершаемый пустым указателем */
Функция endhostent() закрывает соединение с базой.
В пример 11.2 показана программа, осуществляющая последовательный просмотр сетевой базы данных о хостах - узлах сети, а в пример 11.3 приведен фрагмент ее возможной выдачи.
#include <stdio.h>#include <netdb.h> int main (void) { struct hostent *pht; char *pct; int i, j; sethostent (1); while ((pht = gethostent ()) != NULL) { printf ("Официальное имя хоста: %s\n", pht->h_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->h_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Тип адреса хоста: %d\n", pht->h_addrtype); printf ("Длина адреса хоста: %d\n", pht->h_length); printf ("Сетевые адреса хоста:\n"); for (i = 0; (pct = pht->h_addr_list [i]) != NULL; i++) { for (j = 0; j < pht->h_length; j++) { printf (" %d", (unsigned char) pct [j]); } printf ("\n"); } } endhostent (); return 0;}
Листинг 11.2. Пример программы, осуществляющей последовательный доступ к сетевой базе данных о хостах - узлах сети.
Листинг 11.3. Фрагмент возможных результатов работы программы, осуществляющей последовательный доступ к сетевой базе данных о хостах - узлах сети.
К рассматриваемой базе возможен и случайный доступ по ключам - именам и адресам хостов с помощью функций gethostbyname() и gethostbyaddr(), однако они считаются устаревшими и из новой версии стандарта POSIX могут быть исключены. Вместо них предлагается использовать функции getnameinfo() и getaddrinfo() (см. пример 11.4).
Листинг 11.4. Описание функций freeaddrinfo(), getaddrinfo(), getnameinfo().
Функция getaddrinfo() позволяет по имени узла сети (хоста) (аргумент nodename) и/или имени сетевого сервиса (servname) получить набор адресов сокетов и ассоциированную информацию, что дает возможность создать сокет для обращения к заданному сервису.
Если аргумент nodename отличен от пустого указателя, он способен задавать описательное имя или адресную цепочку. Для адресных семейств AF_INET и AF_UNSPEC (см. ниже описание аргумента hints) именем может служить имя хоста, а адресной цепочкой - стандартные для Internet адреса в точечных обозначениях (например, 193.232.173.17).
При пустом значении nodename подразумевается хост, локальный для вызывающего процесса.
Аргумент servname может задавать имя сервиса или (для адресных семейств AF_INET и AF_UNSPEC) десятичный номер порта. Пустое значение servname означает запрос сетевого адреса.
Аргумент hints позволяет передать дополнительную информацию об опрашиваемом сервисе - адресное семейство, тип сокета, протокол, флаги. Согласно стандарту, структура addrinfo, описанная в заголовочном файле <netdb.h>, должна содержать по крайней мере следующие поля.
int ai_flags; /* Входные флаги */ int ai_family; /* Адресное семейство сокета */ int ai_socktype; /* Тип сокета */ int ai_protocol; /* Протокол сокета */ socklen_t ai_addrlen; /* Длина адреса сокета */ struct sockaddr *ai_addr; /* Адрес сокета */ char *ai_canonname; /* Официальное имя узла сети */ struct addrinfo *ai_next; /* Указатель на следующий элемент списка */
При обращении к функции getaddrinfo() все поля структуры addrinfo, на которую указывает аргумент hints, кроме первых четырех (ai_flags, ai_family, ai_socktype, ai_protocol), должны быть нулевыми или равными NULL. Значение AF_UNSPEC в поле ai_family подразумевает, что вызывающего устроит любое адресное семейство. Аналогичный смысл имеют нулевые значения полей ai_socktype и ai_protocol. При hints, равном NULL, подразумевается AF_UNSPEC для ai_family и нулевые значения для других полей.
Из флагов, которые могут быть установлены в поле ai_flags, упомянем следующие.
AI_PASSIVE
Если значение аргумента nodename равно NULL, этот флаг игнорируется. В противном случае, если он указан, будет возвращен адрес сокета, предназначенного для принятия входящих соединений.
AI_CANONNAME
Данный флаг предписывает выяснить официальное имя узла сети.
AI_NUMERICHOST
Флаг означает, что хост задан адресной цепочкой, и не допускает использования какого-либо сервиса имен.
AI_NUMERICSERV
Флаг помечает, что сервис (аргумент servname) задан номером порта, и налагает запрет на обращение к какому-либо сервису имен.
Признаком успешного завершения функции getaddrinfo() является нулевой результат. В таком случае выходной аргумент res будет ссылаться на указатель на список структур типа addrinfo (связанных полем ai_next) - принадлежащие им значения полей ai_family, ai_socktype, ai_protocol пригодны для создания подходящих сокетов с помощью функции socket(), а значения ai_addr и ai_addrlen, в зависимости от флага AI_PASSIVE, могут служить аргументами функций connect() или bind(), применяемых к созданному сокету.
Функция freeaddrinfo() позволяет освободить память, занятую списком структур типа addrinfo и ассоциированными данными.
Функцию getnameinfo() можно считать обратной по отношению к getaddrinfo(). Аргумент sa - входной, он задает транслируемый адрес сокета (salen - размер структуры sockaddr). Аргументы node и service - выходные, задающие адреса областей памяти, куда помещаются, соответственно, имя узла и сервиса; размеры этих областей ограничены значениями nodelen и servicelen.
По умолчанию предполагается, что сокет имеет тип SOCK_STREAM, а кроме того, возвращается полное доменное имя хоста. Аргумент flags позволяет изменить подразумеваемое поведение. Если задан флаг NI_DGRAM, сокет считается датаграммным. При установленном флаге NI_NOFQDN возвращается короткое имя узла. Флаги NI_NUMERICHOST и NI_NUMERICSERV предписывают возвращать числовые цепочки для адресов хоста и сервиса, соответственно.
Обратим внимание на несколько технических деталей. При работе с адресами сокетов вместо родовой структуры типа sockaddr обычно используются более специализированные (описанные в заголовочном файле <netinet/in.h>) - sockaddr_in для адресного семейства AF_INET (IPv4) и sockaddr_in6 для AF_INET6 (IPv6). Первая из них, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля (все с сетевым порядком байт).
Структура типа sockaddr_in6 устроена несколько сложнее; мы не будем ее рассматривать.
Структуры типов sockaddr и sockaddr_in (или sockaddr_in6) мысленно накладываются друг на друга, а преобразование типов между ними выполняется по мере необходимости. Отметим также, что тип in_port_t эквивалентен uint16_t, структура типа in_addr содержит по крайней мере одно поле:
Листинг 11.5. Описание функций преобразования IP-адресов из текстового представления в числовое и наоборот.
Первые две функции манипулируют только адресами IPv4: inet_addr() преобразует текстовую цепочку cp (адрес в стандартных точечных обозначениях) в пригодное для использования в качестве IP-адреса целочисленное значение, inet_ntoa() выполняет обратное преобразование.
Вторая пара функций по сути аналогична первой, но имеет чуть более общий характер, так как способна преобразовывать адреса в формате IPv6. Первый аргумент этих функций, af, задает адресное семейство: AF_INET для IPv4 и AF_INET6 для IPv6. Буфер, на который указывает аргумент dst функции inet_pton() (в него помещается результат преобразования - IP-адрес в числовой двоичной форме с сетевым порядком байт), должен иметь длину не менее 32 бит для адресов IPv4 и 128 бит для IPv6.
Аргумент src функции inet_ntop(), возвращающей текстовое представление, указывает на буфер с IP-адресом в числовой форме с сетевым порядком байт. Аргумент size задает длину выходного буфера, на него указывает аргумент dst. Подходящими значениями, в зависимости от адресного семейства, могут служить INET_ADDRSTRLEN или INET6_ADDRSTRLEN.
Выше было отмечено, что преобразование значений типов uint16_t и uint32_t из хостового порядка байт в сетевой выполняется посредством функций htons() и htonl(); функции ntohs() и ntohl() осуществляют обратную операцию (см. пример 11.6).
Листинг 11.6. Описание функций преобразования целочисленных значений из хостового порядка байт в сетевой и наоборот.
Использование функции getaddrinfo() вместе с сопутствующими техническими деталями проиллюстрируем программой, показанной в пример 11.7. Возможные результаты ее выполнения приведены в пример 11.8.
#include <stdio.h>#include <netdb.h>#include <arpa/inet.h> int main (void) { struct addrinfo hints = {AI_CANONNAME, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; struct addrinfo *addr_res; if (getaddrinfo ("www", "http", &hints, &addr_res) != 0) { perror ("GETADDRINFO"); } else { printf ("Результаты для сервиса http\n"); /* Пройдем по списку возвращенных структур */ do { printf ("Адрес сокета: Порт: %d IP-адрес: %s\n", ntohs (((struct sockaddr_in *) addr_res->ai_addr)->sin_port), inet_ntoa (((struct sockaddr_in *) addr_res->ai_addr)->sin_addr)); printf ("Официальное имя хоста: %s\n", addr_res->ai_canonname); } while ((addr_res = addr_res->ai_next) != NULL); } return 0;}
Листинг 11.7. Пример программы, использующей функцию getaddrinfo().
Результаты для сервиса httpАдрес сокета: Порт: 80 IP-адрес: 193.232.173.1Официальное имя хоста: t01
Листинг 11.8. Возможный результат работы программы, использующей функцию getaddrinfo().
Завершая изложение серии технических моментов, укажем, что полезным дополнением к функциям getaddrinfo() и getnameinfo() является функция gai_strerror() (см. пример 11.9). Она возвращает текстовую цепочку, расшифровывающую коды ошибок, перечисленные в заголовочном файле <netdb.h>. Стандарт POSIX-2001 специфицирует следующие коды ошибок, имена которых говорят сами за себя: EAI_AGAIN, EAI_BADFLAGS, EAI_FAIL, EAI_FAMILY, EAI_MEMORY, EAI_NONAME, EAI_OVERFLOW, EAI_SERVICE, EAI_SOCKTYPE, EAI_SYSTEM.
Если немного модифицировать приведенную выше программу (в пример 11.10 показан измененный фрагмент, где имя сервиса - "HTTP" - задано большими буквами), то в стандартный протокол с помощью функции gai_strerror() будет выдано содержательное диагностическое сообщение (см. пример 11.11). Отметим, что выдача функции perror() в данном случае невразумительна.
Листинг 11.12. Описание функций доступа к базе данных сетей.
Функция getnetent() обслуживает последовательный доступ к базе, getnetbyaddr() осуществляет поиск по адресному семейству (аргумент type) и номеру net сети, а getnetbyname() выбирает сеть с заданным (официальным) именем. Структура типа netent, указатель на которую возвращается в качестве результата этих функций, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля.
char *n_name; /* Официальное имя сети */ char **n_aliases; /* Массив указателей на альтернативные *//* имена сети, завершаемый пустым указателем */ int n_addrtype; /* Адресное семейство (тип адресов) сети */ uint32_t n_net; /* Номер сети (в хостовом порядке байт) */
Точно такой же программный интерфейс предоставляет база данных сетевых протоколов (см. пример 11.13).
Листинг 11.13. Описание функций доступа к базе данных сетевых протоколов.
Структура типа protoent содержит по крайней мере следующие поля.
char *p_name; /* Официальное имя протокола */ char **p_aliases; /* Массив указателей на альтернативные *//* имена протокола, завершаемый пустым *//* указателем */ int p_proto; /* Номер протокола */
В пример 11.14 показан пример программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов. пример 11.15 содержит фрагмент возможных результатов работы этой программы.
#include <stdio.h>#include <netdb.h> int main (void) { struct protoent *pht; char *pct; int i; setprotoent (1); while ((pht = getprotoent ()) != NULL) { printf ("Официальное имя протокола: %s\n", pht->p_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->p_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер протокола: %d\n\n", pht->p_proto); } if ((pht = getprotobyname ("ipv6")) != NULL) { printf ("Номер протокола ipv6: %d\n\n", pht->p_proto); } else { fprintf (stderr, "Протокол ip в базе не найден\n"); } if ((pht = getprotobyname ("IPV6")) != NULL) { printf ("Номер протокола IPV6: %d\n\n", pht->p_proto); } else { fprintf (stderr, "Протокол IPV6 в базе не найден\n"); } endprotoent (); return 0;}
Листинг 11.14. Пример программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов.
Официальное имя протокола: ipАльтернативные имена: IPНомер протокола: 0 Официальное имя протокола: icmpАльтернативные имена: ICMPНомер протокола: 1 . . . Официальное имя протокола: tcpАльтернативные имена: TCPНомер протокола: 6 . . . Официальное имя протокола: udpАльтернативные имена: UDPНомер протокола: 17 . . . Официальное имя протокола: ipv6Альтернативные имена: IPv6Номер протокола: 41 . . . Официальное имя протокола: ipv6-cryptАльтернативные имена: IPv6-CryptНомер протокола: 50 . . . Официальное имя протокола: visaАльтернативные имена: VISAНомер протокола: 70 . . . Официальное имя протокола: iso-ipАльтернативные имена: ISO-IPНомер протокола: 80 . . . Официальное имя протокола: sprite-rpcАльтернативные имена: Sprite-RPCНомер протокола: 90 . . . Официальное имя протокола: ipx-in-ipАльтернативные имена: IPX-in-IPНомер протокола: 111 . . . Официальное имя протокола: fcАльтернативные имена: FCНомер протокола: 133 Номер протокола ipv6: 41 Протокол IPV6 в базе не найден
Листинг 11.15. Фрагмент возможных результатов работы программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов.
Еще одно проявление той же логики работы - база данных сетевых сервисов (см. пример 11.16).
Листинг 11.16. Описание функций доступа к базе данных сетевых сервисов.
Обратим внимание на то, что в данном случае можно указывать второй аргумент поиска - имя протокола. Впрочем, значение аргумента proto может быть пустым указателем, и тогда поиск производится только по имени сервиса (функция getservbyname()) или номеру порта ( getservbyport()), который должен быть задан с сетевым порядком байт.
Структура типа servent содержит по крайней мере следующие поля.
char *s_name; /* Официальное имя сервиса */ char **s_aliases; /* Массив указателей на альтернативные *//* имена сервиса, завершаемый пустым *//* указателем */ int s_port; /* Номер порта, соответствующий сервису *//* (в сетевом порядке байт) */ char *s_proto; /* Имя протокола для взаимодействия с *//* сервисом */
В пример 11.17 приведен пример программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт. В пример 11.18 показан фрагмент возможных результатов работы этой программы.
Листинг 11.17. Пример программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт.
. . . Официальное имя сервиса: ftp-dataАльтернативные имена:Номер порта: 20Имя протокола: tcp Официальное имя сервиса: ftp-dataАльтернативные имена:Номер порта: 20Имя протокола: udp Официальное имя сервиса: ftpАльтернативные имена:Номер порта: 21Имя протокола: tcp Официальное имя сервиса: ftp Альтернативные имена: fsp fspdНомер порта: 21Имя протокола: udp . . . Официальное имя сервиса: kerberosАльтернативные имена: kerberos5 krb5Номер порта: 88Имя протокола: tcp Официальное имя сервиса: kerberosАльтернативные имена: kerberos5 krb5Номер порта: 88Имя протокола: udp . . . Официальное имя сервиса: authАльтернативные имена: authentication tap identНомер порта: 113Имя протокола: tcp Официальное имя сервиса: authАльтернативные имена: authentication tap identНомер порта: 113Имя протокола: udp . . . Официальное имя сервиса: printerАльтернативные имена: spoolerНомер порта: 515Имя протокола: tcp Официальное имя сервиса: printerАльтернативные имена: spoolerНомер порта: 515Имя протокола: udp . . . Официальное имя сервиса: fidoАльтернативные имена:Номер порта: 60179Имя протокола: tcp Официальное имя сервиса: fidoАльтернативные имена:Номер порта: 60179Имя протокола: udp Официальное имя сервиса: ftpАльтернативные имена: fsp fspdНомер порта: 21Имя протокола: udp Официальное имя сервиса: ftpАльтернативные имена:Номер порта: 21Имя протокола: tcp
Листинг 11.18. Фрагмент возможных результатов работы программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт.
Отметим, что при поиске по ключу возвращается первый подходящий элемент базы данных. По этой причине, когда не был задан протокол (второе обращение к функции getservbyport()), в качестве результата был возвращен элемент с протоколом tcp.