Чтение онлайн

ЖАНРЫ

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

Затем

мы задаем имя узла, поддерживающего и IPv4, и IPv6:

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
из предыдущего раздела, мы не пытаемся возвратить ошибку из этой функции.

Поделиться с друзьями: