UNIX: разработка сетевых приложений
Цикл сервераФункция14-22ждет соединения с клиентом. Мы выводим адрес клиента, вызывая функциюaccept. В случае IPv4 или IPv6 эта функция выводит IP-адрес и номер порта. Мы могли бы использовать функциюsock_ntop(описанную в разделе 11.17), чтобы попытаться получить имя узла клиента, но это подразумевает запрос PTR в DNS, что может занять некоторое время, особенно если запрос PTR окажется неудачным. В разделе 14.8 [112] упоминается, что на занятом веб-сервере почти у 25% всех клиентов, соединяющихся с этим сервером, в DNS нет записей типа PTR. Поскольку мы не хотим, чтобы наш сервер (особенно последовательный сервер) в течение нескольких секунд ждал запрос PTR, мы просто выводим IP-адрес и порт.getnameinfoПример: сервер времени и даты с указанием протокола
В листинге 11.7 есть небольшая проблема: первый аргумент функции
— пустой указатель, объединенный с семейством адресовtcp_listen, который задает функцияAF_UNSPEC, — может заставить функциюtcp_listenвозвратить структуру адреса сокета с семейством адресов, отличным от желаемого. Например, первой на узле с двойным стеком будет возвращена структура адреса сокета для IPv6 (см. табл. 11.3), но, возможно, нам требуется, чтобы наш сервер обрабатывал только IPv4.getaddrinfoУ клиентов такой проблемы нет, поскольку клиент должен всегда задавать либо IP-адрес, либо имя узла. Клиентские приложения обычно позволяют пользователю вводить этот параметр как аргумент командной строки. Это дает нам возможность задавать имя узла, связанное с определенным типом IP-адреса (вспомните наши имена узлов -4 и -6 в разделе 11.2), или же задавать либо строку в точечно-десятичной записи (для IPv4), либо шестнадцатеричную строку (для IPv6).
И для серверов существует простая методика, позволяющая нам указать, какой именно протокол следует использовать — IPv4 или IPv6. Для этого нужно позволить пользователю ввести либо IP-адрес, либо имя узла в качестве аргумента командной строки и передать его функции
. В случае IP-адреса строка точечно-десятичной записи IPv4 отличается от шестнадцатеричной строки IPv6. Следующие вызовы функцииgetaddrinfoоказываются либо успешными либо нет, как это показано в данном случае:inet_ptoninet_pton(AF_INET, "0.0.0.0", &foo); /* успешно */inet_pton(AF_INET, "0::0", &foo); /* неудачно*/inet_pton(AF_INET6, "0.0.0.0", &foo); /* неудачно */inet_pton(AF_INET6, "0::0", &foo); /* успешно */Следовательно, если мы изменим наши серверы таким образом, чтобы они получали дополнительный аргумент, то при вводе
% <b>server</b>по умолчанию мы получим IPv6 на узле с двойным стеком, но при вводе
% <b>server 0.0.0.0</b>явно задается IPv4, а при вводе
% <b>server 0::0</b>явно задается IPv6.
В листинге 11.8 показана окончательная версия нашего сервера времени и даты.
Листинг 11.8. Не зависящий от протокола сервер времени и даты, использующий функцию tcp_listen
names/daytimetcpsrv2.c1 #include "unp.h"2 #include <time.h>3 int4 main(int argc, char **argv)5 {6 int listenfd, connfd;7 socklen_t addrlen, len;8 struct sockaddr_storage cliaddr;9 char buff[MAXLINE];10 time_t ticks;11 if (argc == 2)12 listenfd = Tcp_listen(NULL, argv[1], &addrlen);13 else if (argc == 3)14 listenfd = Tcp_listen(argv[1], argv[2], &addrlen);15 else16 err_quit("usage; daytimetcpsrv2 [ <host> ] <service or port>");17 for (;;) {18 len = sizeof(cliaddr);19 connfd = Accept(listenfd, (SA*)&cliaddr, &len);20 printf("connection from %s\n", Sock_ntop((SA*)&cliaddr, len));21 ticks = time(NULL);21 snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));23 Write(connfd, buff, strlen(buff));24 Close(connfd);25 }26 }Обработка аргументов командной строкиЕдинственное изменение по сравнению с листингом 11.6 — это обработка аргументов командной строки, позволяющая пользователю в дополнение к имени службы или порту задавать либо имя узла, либо IP-адрес для связывания с сервером.11-16Сначала мы запускаем этот сервер с сокетом IPv4 и затем соединяемся с сервером от клиентов на двух различных узлах, расположенных в локальной подсети:
freebsd % <b>daytimetcpsrv2 0.0.0.0 9999</b>connection from 192.168.42.2:32961connection from 192.168.42.2:1389А теперь мы запустим сервер с сокетом IPv6:
solaris % <b>daytimetcpsrv2 0::0 9999</b>connection from [3ffe:b80:1f8d:2:204:acff:fe17:bf38]:32964connection from [3ffe:b80:1f8d:2:230:65ff:fe15:caa7]:49601connection from [::ffff:192:168:42:3]:32967connection from [::ffff:192:168:42:3]:49602Первое соединение — от узла
, использующего IPv6, а второе — от узлаaix, использующего IPv6. Два следующих соединения — от узловmacosxиaix, но они используют IPv4, а не IPv6. Мы можем определить это, потому что оба адреса клиента, возвращаемые функциейmacosx, являются адресами IPv4, преобразованными к виду IPv6.accept