Литмир - Электронная Библиотека
Содержание  
A
A
Цикл сервера

14-22
 Функция
accept
ждет соединения с клиентом. Мы выводим адрес клиента, вызывая функцию
sock_ntop
. В случае IPv4 или IPv6 эта функция выводит IP-адрес и номер порта. Мы могли бы использовать функцию
getnameinfo
(описанную в разделе 11.17), чтобы попытаться получить имя узла клиента, но это подразумевает запрос PTR в DNS, что может занять некоторое время, особенно если запрос PTR окажется неудачным. В разделе 14.8 [112] упоминается, что на занятом веб-сервере почти у 25% всех клиентов, соединяющихся с этим сервером, в DNS нет записей типа PTR. Поскольку мы не хотим, чтобы наш сервер (особенно последовательный сервер) в течение нескольких секунд ждал запрос PTR, мы просто выводим IP-адрес и порт.

Пример: сервер времени и даты с указанием протокола

В листинге 11.7 есть небольшая проблема: первый аргумент функции

tcp_listen
 — пустой указатель, объединенный с семейством адресов
AF_UNSPEC
, который задает функция
tcp_listen
, — может заставить функцию
getaddrinfo
возвратить структуру адреса сокета с семейством адресов, отличным от желаемого. Например, первой на узле с двойным стеком будет возвращена структура адреса сокета для IPv6 (см. табл. 11.3), но, возможно, нам требуется, чтобы наш сервер обрабатывал только IPv4.

У клиентов такой проблемы нет, поскольку клиент должен всегда задавать либо IP-адрес, либо имя узла. Клиентские приложения обычно позволяют пользователю вводить этот параметр как аргумент командной строки. Это дает нам возможность задавать имя узла, связанное с определенным типом IP-адреса (вспомните наши имена узлов -4 и -6 в разделе 11.2), или же задавать либо строку в точечно-десятичной записи (для IPv4), либо шестнадцатеричную строку (для IPv6).

И для серверов существует простая методика, позволяющая нам указать, какой именно протокол следует использовать — IPv4 или IPv6. Для этого нужно позволить пользователю ввести либо IP-адрес, либо имя узла в качестве аргумента командной строки и передать его функции

getaddrinfo
. В случае IP-адреса строка точечно-десятичной записи IPv4 отличается от шестнадцатеричной строки IPv6. Следующие вызовы функции
inet_pton
оказываются либо успешными либо нет, как это показано в данном случае:

inet_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.c

 1 #include &quot;unp.h&quot;

 2 #include &lt;time.h&gt;

 3 int

 4 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], &amp;addrlen);

13  else if (argc == 3)

14   listenfd = Tcp_listen(argv[1], argv[2], &amp;addrlen);

15  else

16   err_quit(&quot;usage; daytimetcpsrv2 [ &lt;host&gt; ] &lt;service or port&gt;&quot;);

17  for (;;) {

18   len = sizeof(cliaddr);

19   connfd = Accept(listenfd, (SA*)&amp;cliaddr, &amp;len);

20   printf(&quot;connection from %s\n&quot;, Sock_ntop((SA*)&amp;cliaddr, len));

21   ticks = time(NULL);

21   snprintf(buff, sizeof(buff), &quot;%.24s\r\n&quot;, ctime(&amp;ticks));

23   Write(connfd, buff, strlen(buff));

24   Close(connfd);

25  }

26 }

Обработка аргументов командной строки

11-16
 Единственное изменение по сравнению с листингом 11.6 — это обработка аргументов командной строки, позволяющая пользователю в дополнение к имени службы или порту задавать либо имя узла, либо IP-адрес для связывания с сервером.

Сначала мы запускаем этот сервер с сокетом IPv4 и затем соединяемся с сервером от клиентов на двух различных узлах, расположенных в локальной подсети:

freebsd % <b>daytimetcpsrv2 0.0.0.0 9999</b>

connection from 192.168.42.2:32961

connection from 192.168.42.2:1389

А теперь мы запустим сервер с сокетом IPv6:

solaris % <b>daytimetcpsrv2 0::0 9999</b>

connection from [3ffe:b80:1f8d:2:204:acff:fe17:bf38]:32964

connection from [3ffe:b80:1f8d:2:230:65ff:fe15:caa7]:49601

connection from [::ffff:192:168:42:3]:32967

connection from [::ffff:192:168:42:3]:49602

Первое соединение — от узла

aix
, использующего IPv6, а второе — от узла
macosx
, использующего IPv6. Два следующих соединения — от узлов
aix
и
macosx
, но они используют IPv4, а не IPv6. Мы можем определить это, потому что оба адреса клиента, возвращаемые функцией
accept
, являются адресами IPv4, преобразованными к виду IPv6.

134
{"b":"225366","o":1}