KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программное обеспечение » Уильям Стивенс - UNIX: разработка сетевых приложений

Уильям Стивенс - UNIX: разработка сетевых приложений

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Уильям Стивенс, "UNIX: разработка сетевых приложений" бесплатно, без регистрации.
Перейти на страницу:

freebsd % testga -р -s 8888 -t stream


socket(AF_INET6, SOCK_STREAM, 6)

address: [::]:8888


socket(AF_INET, SOCK_STREAM, 6)

address: 0.0.0.0:8888

Возвращаются две структуры. Поскольку мы запустили эту программу на узле, поддерживающем и IPv4, и IPv6, не задав семейства адресов, функция getaddrinfo возвращает универсальный адрес IPv6 и универсальный адрес IPv4. Структура IPv6 возвращается перед структурой IPv4, поскольку, как мы увидим в главе 12, клиент или сервер IPv6 на узле с двойным стеком может взаимодействовать с собеседниками по IPv6 и по IPv4.

11.11. Функция host_serv

Наш первый интерфейс функции getaddrinfo не требует от вызывающего процесса размещать в памяти структуру рекомендаций и заполнять ее. Вместо этого аргументами нашей функции host_serv будут интересующие нас поля — семейство адресов и тип сокета.

#include "unp.h"


struct addrinfo *host_serv(const char *hostname, const char *service, int family, int socktype);

Возвращает: в случае успешного выполнения указатель на структуру addrinfo. NULL в случае ошибки

В листинге 11.3 показан исходный код этой функции.

Листинг 11.3. Функция host_serv

//lib/host_serv.c

 1 #include "unp.h"


 2 struct addrinfo*

 3 host_serv(const char *host, const char *serv, int family, int socktype)

 4 {

 5  int n;

 6  struct addrinfo hints, *res;


 7  bzero(&hints, sizeof(struct addrinfo));

 8  hints.ai_flags = AI_CANONNAME; /* всегда возвращает каноническое имя */

 9  hints.ai_family = family; /* AF_UNSPEC, AF_INET, AF_INET6, ... */

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

11.12. Функция tcp_connect

Теперь мы напишем две функции, использующие функцию getaddrinfo для обработки большинства сценариев клиентов и серверов TCP, которые мы создаем. Первая из этих функций, tcp_connect, выполняет обычные шаги клиента: создание сокета TCP и соединение с сервером.

#include "unp.h"


int tcp_connect(const char *hostname, const char *service);

Возвращает: в случае успешного соединения - дескриптор присоединенного сокета, в случае ошибки не возвращается ничего

В листинге 11.4 показан исходный код.

Листинг 11.4. Функция tcp_connect: выполнение обычных шагов клиента

//lib/tcp_connect.c

 1 #include "unp.h"


 2 int

 3 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 до успешного выполнения или до окончания списка

14-25 Затем пробуется каждый IP-адрес: вызываются функции socket и connect. Если выполнение функции socket неудачно, это не фатальная ошибка, так как такое может случиться, если был возвращен адрес IPv6, а ядро узла не поддерживает IPv6. Если выполнение функции connect успешно, выполняется функция break для выхода из цикла. В противном случае, после того как перепробованы все адреса, цикл также завершается. Функция freeaddrinfo освобождает всю динамически выделенную память.

Эта функция (как и другие наши функции, предоставляющие более простой интерфейс для функции getaddrinfo в следующих разделах) завершается, если либо оказывается неудачным вызов функции getaddrinfo, либо вызов функции connect не выполняется успешно. Возвращение из нашей функции возможно лишь в случае успешного выполнения. Было бы сложно возвратить код ошибки (одну из констант EAI_xxx), не добавляя еще одного аргумента. Это значит, что наша функция-обертка тривиальна:

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

//names/daytimetcpcli.c

 1 #include "unp.h"


 2 int

 3 main(int argc, char **argv)

 4 {

 5  int sockfd, n;

 6  char recvline[MAXLINE + 1];

 7  socklen_t len;

 8  struct sockaddr_storage *ss;


 9  if (argc != 3)

10   err_quit

11    ("usage, daytimetcpcli <hostname/IPaddress> <service/port#>");


12  sockfd = Tcp_connect(argv[1], argv[2]);


13  len = sizeof(ss);

14  Getpeername(sockfd, (SA*)&ss, &len);

15  printf("connected to %sn", Sock_ntop_host((SA*)&ss, len));


16  while ((n = Read(sockfd, recvline, MAXLINE)) > 0) {

17   recvline[n] = 0; /* завершающий нуль */

18   Fputs(recvline, stdout);

19  }

20  exit(0);

21 }

Аргументы командной строки

9-11 Теперь нам требуется второй аргумент командной строки для задания либо имени службы, либо номера порта, что позволит нашей программе соединяться с другими портами.

Соединение с сервером

12 Теперь весь код сокета для этого клиента выполняется функцией tcp_connect.

Вывод ответа сервера

13-15 Мы вызываем функцию getpeername, чтобы получить адрес протокола сервера и вывести его. Мы делаем это для проверки протокола, используемого в примерах, которые скоро покажем.

Обратите внимание, что функция tcp_connect не возвращает размера структуры адреса сокета, который использовался для функции connect. Мы могли добавить еще один аргумент-указатель, чтобы получить это значение, но при создании этой функции мы стремились добиться меньшего числа аргументов, чем у функции getaddrinfo. Поэтому мы определяем константу MAXSOCKADDR в нашем заголовке unp.h так, чтобы ее размер равнялся размеру наибольшей структуры адреса сокета. Обычно это размер структуры адреса доменного сокета Unix (см. раздел 14.2), немного более 100 байт. Мы выделяем в памяти пространство для структуры указанного размера и заполняем ее с помощью функции getpeername.

Эта версия нашего клиента работает и с IPv4, и с IPv6, тогда как версия, представленная в листинге 1.1, работала только с IPv4, а версия из листинга 1.2 — только с IPv6. Сравните нашу новую версию с представленной в листинге Д.6, которую мы написали, чтобы использовать функции gethostbyname и getservbyname для поддержки и IPv4, и IPv6.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*