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

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

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

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

В случае присоединенного сокета UDP два последних аргумента, которые требовались в функции udp_client, больше не нужны. Вызывающий процесс может вызвать функцию write вместо sendto, таким образом нашей функции не нужно возвращать структуру адреса сокета и ее длину. В листинге 11.11 представлен исходный код.

Листинг 11.11. Функция udp_connect: создание присоединенного сокета UDP

//lib/udp_connect.c

 1 #include "unp.h"


 2 int

 3 udp_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_DGRAM;


10  if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)

11   err_quit("udp_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("udp_connect error for %s, %s", host, serv);


24  freeaddrinfo(ressave);


25  return (sockfd);

26 }

Эта функция почти идентична функции tcp_connect. Однако отличие в том, что при вызове функции connect для сокета UDP ничего не отправляется собеседнику. Если что-то не в порядке (собеседник недоступен или на заданном порте не запущен сервер), вызывающий процесс не обнаружит этого, пока не пошлет собеседнику дейтаграмму.

11.16. Функция udp_server

Наша последняя функция, предоставляющая более простой интерфейс для функции getaddrinfo, — это функция udp_server.

#include "unp.h"


int udp_server(const char *hostname, const char *service, socklen_t *lenptr);

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

Аргументы функции те же, что и для функции tcp_listen: необязательный hostname, обязательный service (для связывания номер порта) и необязательный указатель на переменную, в которой возвращается размер структуры адреса сокета. В листинге 11.12 представлен исходный код.

Листинг 11.12. Функция udp_server: создание неприсоединенного сокета для сервера UDP

//lib/udp_server.c

 1 #include "unp.h"


 2 int

 3 udp_server(const char *host, const char *serv, socklen_t *addrlenp)

 4 {

 5  int sockfd, n;

 6  struct addrinfo hints, *res, *ressave;


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

 8  hints.ai_flags = AI_PASSIVE;

 9  hints.ai_family = AF_UNSPEC;

10  hints.ai_socktype = SOCK_DGRAM;


11  if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)

12   err_quit("udp_server error for %s, %s: %s",

13    host, serv, gai_strerror(n));

14  ressave = res;


15  do {

16   sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

17   if (sockfd < 0)

18    continue; /* ошибка, пробуем следующий адрес */


19   if (bind(sockfd, res->ai_addr, res->ai_addrlen) == 0)

20    break; /* успех */


21   Close(sockfd); /* ошибка при вызове функции bind, закрываем

                       сокет и пробуем следующий адрес */

22  } while ((res = res->ai_next) != NULL);


23  if (res == NULL) /* значение errno устанавливается при

                        последнем вызове функции socket() or bind() */

24   err_sys("udp_server error for %s, %s", host, serv);


25  if (addrlenp)

26   *addrlenp = res->ai_addrlen; /* возвращается размер адреса

                                     протокола */

27  freeaddrinfo(ressave);


28  return (sockfd);

29 }

Эта функция практически идентична функции tcp_listen, в ней нет только вызова функции listen. Мы устанавливаем семейство адресов AF_UNSPEC, но вызывающий процесс может использовать ту же технологию, которую мы описали при рассмотрении листинга 11.6, чтобы потребовать использование определенного протокола (IPv4 или IPv6).

Мы не устанавливаем параметр сокета SO_REUSEADDR для сокета UDP, поскольку этот параметр сокета может допустить связывание множества сокетов с одним и тем же портом UDP на узлах, поддерживающих многоадресную передачу, как мы говорили в разделе 7.5. Поскольку у сокета UDP нет аналога состояния TIME_WAIT, свойственного сокетам TCP, нет необходимости устанавливать этот параметр при запуске сервера.

Пример: не зависящий от протокола UDP-сервер времени и даты

В листинге 11.13 представлен наш сервер времени и даты, полученный путем модификации листинга 11.8 и предназначенный для использования UDP.

Листинг 11.13. Не зависящий от протокола UDP-сервер времени и даты

//names/daytimeudpsrv2.c

 1 #include "unp.h"

 2 #include <time.h>


 3 int

 4 main(int argc, char **argv)

 5 {

 6  int sockfd;

 7  ssize_t n;

 8  char buff[MAXLINE];

 9  time_t ticks;

10  socklen_t addrlen, len;

11  struct sockaddr_storage cliaddr;


12  if (argc == 2)

13   sockfd = Udp_server(NULL, argv[1], &addrlen);

14  else if (argc == 3)

15   sockfd = Udp_server(argv[1], argv[2], &addrlen);

16  else

17   err_quit("usage: daytimeudpsrv [ <host> ] <service or port>");


18  for (;;) {

19   len = sizeof(cliaddr);

20   n = Recvfrom(sockfd, buff, MAXLINE, 0, (SA*)&cliaddr, &len);

21   printf("datagram from %sn", Sock_ntop((SA*)&cliaddr, len));


22   ticks = time(NULL);

23   snprintf(buff, sizeof(buff), "% 24srn", ctime(&ticks));

24   Sendto(sockfd, buff, strlen(buff), 0, (SA*)&cliaddr, len);

25  }

26 }

11.17. Функция getnameinfo

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

#include <netdb.h>


int getnameinfo(const struct sockaddr *sockaddr, socklen_t addrlen, char *host,

 size_t hostlen, char *serv, size_t servlen, int flags);

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

Аргумент sockaddr указывает на структуру адреса сокета, содержащую адрес протокола, преобразуемый в строку, удобную для человеческого восприятия, а аргумент addrlen содержит длину этой структуры. Эта структура и ее длина обычно возвращаются любой из следующих функций: accept, recvfrom, getsockname или getpeername.

Вызывающий процесс выделяет в памяти пространство для двух строк, удобных для человеческого восприятия: аргументы host и hostlen определяют строку, описывающую узел, а аргументы serv и servlen определяют строку, которая описывает службы. Если вызывающему процессу не нужна возвращаемая строка с описанием узла, задается нулевая длина этой строки (hostlen). Аналогично, нулевое значение аргумента servlen означает, что не нужно возвращать информацию о службе.

Разница между функциями sock_ntop и getnameinfo состоит в том, что первая не задействует DNS, а только возвращает IP-адрес и номер порта. Последняя же обычно пытается получить имя и для узла, и для службы.

В табл. 11.4 показаны шесть флагов, которые можно задать для изменения действия, выполняемого функцией getnameinfo.


Таблица 11.4. Флаги функции getnameinfo

Константа Описание NI_DGRAM Дейтаграммный сокет NI_NAMEREQD Возвращать ошибку, если невозможно получить имя узла по его адресу NI_NOFQDN Возвращать только ту часть FQDN, которая содержит имя узла NI_NUMERICHOST Возвращать численное значение адреса вместо имени узла NI_NUMERICSCOPE Возвращать численное значение идентификатора области NI_NUMERICSERV Возвращать номер порта вместо имени службы

■ Флаг NI_DGRAM должен быть задан, когда вызывающий процесс знает, что работает с дейтаграммным сокетом. Причина в том, что если функции getnameinfo задать только IP-адрес и номер порта в структуре адреса сокета, она не сможет определить протокол (TCP или UDP). Существует несколько номеров портов, которые в случае TCP задействованы для одной службы, а в случае UDP для совершенно другой. Примером может служить порт 514, используемый службой rsh в TCP и службой syslog в UDP.

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