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.