UNIX: разработка сетевых приложений
10 hints.ai_socktype = socktype; /* 0, SOCK_STREAM, SOCK_DGRAM, ... */11 if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)12 return (NULL);13 return (res); /* возвращает указатель на первый элемент в связномсписке */14 }Функция инициализирует структуру рекомендаций (7-13), вызывает функциюhintsи возвращает пустой указатель, если происходит ошибка.getaddrinfoМы вызываем эту функцию в листинге 16.11, когда нам нужно использовать
для получения информации об узле и о службе и при этом мы хотим установить соединение самостоятельно.getaddrinfo11.12. Функция tcp_connect
Теперь мы напишем две функции, использующие функцию
для обработки большинства сценариев клиентов и серверов TCP, которые мы создаем. Первая из этих функций,getaddrinfo, выполняет обычные шаги клиента: создание сокета TCP и соединение с сервером.tcp_connect#include "unp.h"int tcp_connect(const char *<i>hostname</i>, const char *<i>service</i>);<i>Возвращает: в случае успешного соединения - дескриптор присоединенного сокета, в случае ошибки не возвращается ничего</i>В листинге 11.4 показан исходный код.
Листинг 11.4. Функция tcp_connect: выполнение обычных шагов клиента
/
/lib/tcp_connect.c1 #include "unp.h"2 int3 tcp_connect(const char *host, const char *serv)4 {5 int sockfd, n;6 struct addrinfo hints, *res, *ressave;7 bzero(&hints, sizeof(struct addrinfo));8 hints.ai_family = AF_UNSPEC;9 hints.ai_socktype = SOCK_STREAM;10 if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)11 err_quit("tcp_connect error for %s, %s: %s",12 host, serv, gai_strerror(n));13 ressave = res;14 do {15 sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);16 if (sockfd < 0)17 continue; /* игнорируем этот адрес */18 if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)19 break; /* успех */20 Close(sockfd); /* игнорируем этот адрес */21 } while ((res = res->ai_next) != NULL);22 if (res == NULL) /* значение errno устанавливается припоследней попытке connect() */23 err_sys("tcp_connect error for %s, %s", host, serv);24 freeaddrinfo(ressave);25 return (sockfd);26 }Вызов функции getaddrinfoфункция7-13вызывается один раз, когда мы задаем семейство адресовgetaddrinfoи тип сокетаAF_UNSPEC.SOCK_STREAMПеребор всех структур addrinfo до успешного выполнения или до окончания спискаЗатем пробуется каждый IP-адрес: вызываются функции14-25иsocket. Если выполнение функцииconnectнеудачно, это не фатальная ошибка, так как такое может случиться, если был возвращен адрес IPv6, а ядро узла не поддерживает IPv6. Если выполнение функцииsocketуспешно, выполняется функцияconnectдля выхода из цикла. В противном случае, после того как перепробованы все адреса, цикл также завершается. Функцияbreakосвобождает всю динамически выделенную память.freeaddrinfoЭта функция (как и другие наши функции, предоставляющие более простой интерфейс для функции
в следующих разделах) завершается, если либо оказывается неудачным вызов функцииgetaddrinfo, либо вызов функцииgetaddrinfoне выполняется успешно. Возвращение из нашей функции возможно лишь в случае успешного выполнения. Было бы сложно возвратить код ошибки (одну из константconnect), не добавляя еще одного аргумента. Это значит, что наша функция-обертка тривиальна:EAI_<i>xxx</i>Tcp_connect(const char *host, const char *serv) {return(tcp_connect(host, serv));}Тем не менее мы по-прежнему вызываем функцию-обертку вместо функции
ради сохранения единообразия в оставшейся части книги.tcp_connectПРИМЕЧАНИЕПроблема с возвращаемым значением заключается в том, что дескрипторы неотрицательные, но мы не знаем, положительны или отрицательны значения EAI_xxx. Если бы эти значения были положительными, мы могли бы возвратить равные им по абсолютной величине отрицательные значения, когда вызов функции getaddrinfo окажется неудачным. Но мы также должны возвратить некое другое отрицательное значение, чтобы указать, что все структуры были перепробованы безуспешно.
Пример: клиент времени и даты
В листинге 11.5 показан наш клиент времени и даты из листинга 1.1, переписанный с использованием функции
.tcp_connectЛистинг 11.5. Клиент времени и даты, переписанный с использованием функции tcp_connect