UNIX: разработка сетевых приложений
Сначала мы изменяем функцию клиента
(см. листинг 8.3) для работы со стандартным эхо-сервером (см. табл. 2.1). Мы просто заменяем присваиваниеmainservaddr.sin_port = htons(SERV_PORT);присваиванием
servaddr.sin_port = htons(7);Теперь мы можем использовать с нашим клиентом любой узел, на котором работает стандартный эхо-сервер.
Затем мы переписываем функцию
, с тем чтобы она размещала в памяти другую структуру адреса сокета для хранения структуры, возвращаемой функциейdg_cli. Мы показываем ее в листинге 8.5.recvfromЛистинг 8.5. Версия функции dg_cli, проверяющая возвращаемый адрес сокета
//udpcliserv/dgcliaddr.c1 #include "unp.h"2 void3 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)4 {5 int n;6 char sendline[MAXLINE], recvline[MAXLINE + 1];7 socklen_t len;8 struct sockaddr *preply_addr;9 preply_addr = Malloc(servlen);10 while (Fgets(sendline, MAXLINE, fp) != NULL) {11 Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);12 len = servlen;13 n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);14 if (len != servlen || memcmp(pservaddr, preply_addr, len) != 0) {15 printf("reply from %s (ignored)\n",16 continue;17 }18 recvline[n] = 0; /* завершающий нуль */19 Fputs(recvline, stdout);20 }21 }Размещение другой структуры адреса сокета в памятиМы размещаем в памяти другую структуру адреса сокета при помощи функции9. Обратите внимание, что функцияmallocвсе еще является не зависящей от протокола. Поскольку нам не важно, с каким типом структуры адреса сокета мы имеем дело, мы используем в вызове функцииdg_cliтолько ее размер.mallocСравнение возвращаемых адресовВ вызове функции12-13мы сообщаем ядру, что нужно возвратить адрес отправителя дейтаграммы. Сначала мы сравниваем длину, возвращаемую функциейrecvfromв аргументе типа «значение-результат», а затем сравниваем сами структуры адреса сокета при помощи функцииrecvfrom.memcmpНовая версия нашего клиента работает замечательно, если сервер находится на узле с одним единственным IP-адресом. Но эта программа может не сработать, если сервер имеет несколько сетевых интерфейсов (multihomed server). Запускаем эту программу, обращаясь к узлу
, у которого имеется два интерфейса и два IP-адреса:freebsd4macosx % <b>host freebsd4</b>freebsd4.unpbook.com has address 172.24.37.94freebsd4.unpbook.com has address 135.197.17.100macosx % <b>udpcli02 135.197.17.100</b><b>hello</b>reply from 172.24.37.94:7 (ignored)<b>goodbye</b>reply from 172.24.37.94:7 (ignored)По рис. 1.7 видно, что мы задали IP-адрес из другой подсети. Обычно это допустимо. Большинство реализаций IP принимают приходящую IP-дейтаграмму, предназначенную для любого из IP-адресов узла, независимо от интерфейса, на который она приходит [128, с. 217-219]. Документ RFC 1122 [10] называет это моделью системы с гибкой привязкой (weak end system model). Если система должна реализовать то, что в этом документе называется моделью системы с жесткой привязкой (strong end system model), она принимает приходящую дейтаграмму, только если дейтаграмма приходит на тот интерфейс, которому она адресована.
IP-адрес, возвращаемый функцией
(IP-адрес отправителя дейтаграммы UDP), не является IP-адресом, на который мы посылали дейтаграмму. Когда сервер отправляет свой ответ, IP-адрес получателя — это адрес 172.24.37.94. Функция маршрутизации внутри ядра на узлеrecvfromвыбирает адрес 172.24.37.94 в качестве исходящего интерфейса. Поскольку сервер не связал IP-адрес со своим сокетом (сервер связал со своим сокетом универсальный адрес, что мы можем проверить, запустив программуfreebsd4на узлеnetstat), ядро выбирает адрес отправителя дейтаграммы IP. Этим адресом становится первичный IP-адрес исходящего интерфейса [128, с. 232-233]. Если мы отправляем дейтаграмму не на первичный IP-адрес интерфейса (то есть на альтернативное имя, псевдоним), то наша проверка, показанная в листинге 8.5, также окажется неудачной.freebsd4Одним из решений будет проверка клиентом доменного имени отвечающего узла вместо его IP-адреса. Для этого имя сервера ищется в DNS (см. главу 11) на основе IP-адреса, возвращаемого функцией
. Другое решение — сделать так, чтобы сервер UDP создал по одному сокету для каждого IP-адреса, сконфигурированного на узле, связал с помощью функцииrecvfromэтот IP-адрес с сокетом, вызвал функциюbindдля каждого из всех этих сокетов (ожидая, когда какой-либо из них станет готов для чтения), а затем ответил с сокета, готового для чтения. Поскольку сокет, используемый для ответа, связан с IP-адресом, который являлся адресом получателя клиентского запроса (иначе дейтаграмма не была бы доставлена на сокет), мы можем быть уверены, что адреса отправителя ответа и получателя запроса совпадают. Мы показываем эти примеры в разделе 22.6.selectПРИМЕЧАНИЕВ системе Solaris с несколькими сетевыми интерфейсами IP-адрес отправителя ответа сервера — это IP-адрес получателя клиентского запроса. Сценарий, описанный в данном разделе, относится к реализациям, происходящим от Беркли, которые выбирают IP-адрес отправителя, основываясь на исходящем интерфейсе.
8.9. Запуск клиента без запуска сервера
Следующий сценарий, который мы рассмотрим, — это запуск клиента без запуска сервера. Если мы сделаем так и введем одну строку на стороне клиента, ничего не будет происходить. Клиент навсегда блокируется в своем вызове функции
, ожидая ответа сервера, который никогда не придет. Но в данном примере это не имеет значения, поскольку сейчас мы стремимся глубже понять протоколы и выяснить, что происходит с нашим сетевым приложением.recvfrom