UNIX: разработка сетевых приложений
Шрифт:
Эта версия нашего клиента работает и с IPv4, и с IPv6, тогда как версия, представленная в листинге 1.1, работала только с IPv4, а версия из листинга 1.2 — только с IPv6. Сравните нашу новую версию с представленной в листинге Д.6, которую мы написали, чтобы использовать функции
gethostbyname
и getservbyname
для поддержки и IPv4, и IPv6. Сначала мы задаем имя узла, поддерживающего только IPv4:
freebsd % daytimetcpcli linux daytime
connected to 206 168.112.96
Sun Jul 27 23:06:24 2003
Затем
freebsd % daytimetcpcli aix daytime
connected to 3ffe:b80:1f8d:2:204:acff:fe17:bf38
Sun Jul 27 23:17:13 2003
Используется адрес IPv6, поскольку у узла имеется и запись типа AAAA, и запись типа А. Кроме того, функция
tcp_connect
устанавливает семейство адресов AF_UNSPEC
, поэтому, как было отмечено в табл. 11.3, сначала идет поиск записей типа AAAA, и только если этот поиск неудачен, выполняется поиск записей типа А. В следующем примере мы указываем на необходимость использования именно адреса IPv4, задавая имя узла с суффиксом
– 4
, что, как мы отмечали в разделе 11.2, в соответствии с принятым нами соглашением означает имя узла, который поддерживает только записи типа А:
freebsd % daytimetcpcli aix-4 daytime
connected to 192.168.42.2
Sun Jul 27 23:17:48 2003
11.13. Функция tcp_listen
Наша следующая функция,
tcp_listen
, выполняет обычные шаги сервера TCP: создание сокета TCP, связывание его с заранее известным портом с помощью функции bind и разрешение приема входящих запросов через соединение. В листинге 11.6 представлен исходный код.
#include "unp.h"
int tcp_listen(const char * hostname, const char * service, socklen_t * lenptr);
В случае успешного выполнения возвращает дескриптор присоединенного сокета, в случае ошибки не возвращает ничего
Листинг 11.6. Функция tcp_listen: выполнение обычных шагов сервера TCP
//lib/tcp_listen.c
1 #include "unp.h"
2 int
3 tcp_listen(const char *host, const char *serv, socklen_t *addrlenp)
4 {
5 int listenfd, n;
6 const int on = 1;
7 struct addrinfo hints, *res, *ressave;
8 bzero(&hints, sizeof(struct addrinfo));
9 hints.ai_flags = AI_PASSIVE;
10 hints.ai_family = AF_UNSPEC;
11 hints.ai_socktype = SOCK_STREAM;
12 if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
13 err_quit("tcp_listen error for %s, %s: %s",
14 host, serv, gai_strerror(n));
15 ressave = res;
16 do {
17 listenfd =
18 socket(res->ai_family, res->ai_socktype, res->ai_protocol);
19 if (listenfd < 0)
20 continue; /*
ошибка, пробуем следующий адрес */
21 Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
22 if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0)
23 break; /* успех */
24 Close(listenfd); /* ошибка при вызове функции bind, закрываем
сокет и пробуем следующий адрес*/
25 } while ((res = res->ai_next) != NULL);
26 if (res == NULL) /* значение errno устанавливается при последнем
вызове функции socket или bind */
27 err_sys("tcp_listen error for %s, %s", host, serv);
28 Listen(listenfd, LISTENQ);
29 if (addrlenp)
30 *addrlenp = res->ai_addrlen; /* возвращает размер адреса протокола */
31 freeaddrinfo(ressave);
32 return (listenfd);
33 }
Вызов функции getaddrinfo
8-15
Мы инициализируем структуру addrinfo
с учетом следующих рекомендаций (элементов структуры hints
): AI_PASSIVE
, поскольку это функция для сервера, AF_UNSPEC
для семейства адресов и SOCK_STREAM
. Вспомните табл. 11.3: если имя узла не задано (что вполне нормально для сервера, который хочет связать с дескриптором универсальный адрес), то наличие значений AI_PASSIVE
и AF_UNSPEC
вызовет возвращение двух структур адреса сокета: первой для IPv6 и второй для IPv4 (в предположении, что это узел с двойным стеком). Создание сокета и связывание с адресом
16-24
Вызываются функции socket
и bind
. Если любой из вызовов окажется неудачным, мы просто игнорируем данную структуру addrinfo
и переходим к следующей. Как было сказано в разделе 7.5, для сервера TCP мы всегда устанавливаем параметр сокета SO_REUSEADDR
. Проверка на наличие ошибки
25-26
Если все вызовы функций socket
и bind
окажутся неудачными, мы сообщаем об ошибке и завершаем выполнение. Как и в случае с нашей функцией tcp_connect
из предыдущего раздела, мы не пытаемся возвратить ошибку из этой функции.
Поделиться с друзьями: