UNIX: разработка сетевых приложений
38 continue; /* назад в for() */39 else40 err_sys("select error");41 }42 if (FD_ISSET(listenfd, &rset)) {43 len = sizeof(cliaddr);44 connfd = Accept(listenfd, (SA*)&cliaddr, &len);45 if ((childpid = Fork()) == 0) { /* дочерний процесс */46 Close(listenfd); /* закрывается прослушиваемый сокет */47 str_echo(connfd); /* обработка запроса */48 exit(0);49 }50 Close(connfd); /* родитель закрывает присоединенный сокет */51 }52 if (FD_ISSET(udpfd, &rset)) {53 len = sizeof(cliaddr);54 n = Recvfrom(udpfd, mesg, MAXLINE, 0, (SA*)&cliaddr, &len);55 Sendto(udpfd, mesg, n, 0, (SA*)&cliaddr, len);56 }57 }58 }Установка обработчика сигнала SIGCHLDДля сигнала30устанавливается обработчик, поскольку соединения TCP будут обрабатываться дочерним процессом. Этот обработчик сигнала мы показали в листинге 5.8.SIGCHLDПодготовка к вызову функции selectМы инициализируем набор дескрипторов для функции31-32и вычисляем максимальный из двух дескрипторов, готовности которого будем ожидать.selectВызов функции selectМы вызываем функцию34-41, ожидая только готовности к чтению прослушиваемого сокета TCP или сокета UDP. Поскольку наш обработчик сигналаselectможет прервать вызов функцииsig_chld, обрабатываем ошибкуselect.EINTRОбработка нового клиентского соединенияС помощью функции42-51мы принимаем новое клиентское соединение, а когда прослушиваемый сокет TCP готов для чтения, с помощью функцииacceptпорождаем дочерний процесс и вызываем нашу функциюforkв дочернем процессе. Это та же последовательность действий, которую мы выполняли в главе 5.str_echoОбработка приходящей дейтаграммыЕсли сокет UDP готов для чтения, дейтаграмма пришла. Мы читаем ее с помощью функции52-57и отправляем обратно клиенту с помощью функцииrecvfrom.sendto8.16. Резюме
Преобразовать наши эхо-клиент и эхо-сервер так, чтобы использовать UDP вместо TCP, оказалось несложно. Но при этом мы лишились множества возможностей, предоставляемых протоколом TCP: определение потерянных пакетов и повторная передача, проверка, приходят ли пакеты от корректного собеседника, и т.д. Мы возвратимся к этой теме в разделе 22.5 и увидим, как можно улучшить надежность приложения UDP.
Сокеты UDP могут генерировать асинхронные ошибки, то есть ошибки, о которых сообщается спустя некоторое время после того, как пакет был отправлен. Сокеты TCP всегда сообщают приложению о них, но в случае UDP для получения этих ошибок сокет должен быть присоединенным.
В UDP отсутствует возможность управления потоком, что очень легко продемонстрировать. Обычно это не создает проблем, поскольку многие приложения UDP построены с использованием модели «запрос-ответ» и не предназначены для передачи большого количества данных.
Есть еще ряд моментов, которые нужно учитывать при написании приложений UDP, но мы рассмотрим их в главе 22 после описания функций интерфейсов, широковещательной и многоадресной передачи.
Упражнения
1. Допустим, у нас имеется два приложения, одно использует TCP, а другое — UDP. В приемном буфере сокета TCP находится 4096 байт данных, а в приемном буфере для сокета UDP — две дейтаграммы по 2048 байт. Приложение TCP вызывает функцию
с третьим аргументом 4096, а приложение UDP вызывает функциюreadс третьим аргументом 4096. Есть ли между этими вызовами какая-нибудь разница?recvfrom2. Что произойдет в листинге 8.2, если мы заменим последний аргумент функции
(который мы обозначилиsendto) аргументомlen?clilen3. Откомпилируйте и запустите сервер UDP из листингов 8.1 и 8.4, а затем — клиент из листингов 8.3 и 8.4. Убедитесь в том, что клиент и сервер работают вместе.
4. Запустите программу
в одном окне, задав параметрping(отправка одного пакета каждые 60 секунд; некоторые системы используют ключ-i 60вместоI), параметрi(вывод всех полученных сообщений об ошибках ICMP) и задав адрес закольцовки на себя (обычно 127.0.0.1). Мы будем использовать эту программу, чтобы увидеть ошибку ICMP недоступности порта, возвращаемую узлом сервера. Затем запустите наш клиент из предыдущего упражнения в другом окне, задав IP-адрес некоторого узла, на котором не запущен сервер. Что происходит?-v5. Рассматривая рис. 8.3, мы сказали, что каждый присоединенный сокет TCP имеет свой собственный буфер приема. Как вы думаете, есть ли у прослушиваемого сокета свой собственный буфер приема?
6. Используйте программу
(см. раздел В.3) и такое средство, как, например,sock(см. раздел В.5), чтобы проверить утверждение из раздела 8.10: если клиент с помощью функцииtcpdumpсвязывает IP-адрес со своим сокетом, но отправляет дейтаграмму, исходящую от другого интерфейса, то результирующая дейтаграмма содержит IP-адрес, который был связан с сокетом, даже если он не соответствует исходящему интерфейсу.bind7. Откомпилируйте программы из раздела 8.13 и запустите клиент и сервер на различных узлах. Помещайте
в клиент каждый раз, когда дейтаграмма записывается в сокет. Изменяет ли это процент полученных пакетов? Почему? Вызывайтеprintfиз сервера каждый раз, когда дейтаграмма читается из сокета. Изменяет ли это процент полученных пакетов? Почему?printf8. Какова наибольшая длина, которую мы можем передать функции
для сокета UDP/IPv4, то есть каково наибольшее количество данных, которые могут поместиться в дейтаграмму UDP/IPv4? Что изменяется в случае UDP/IPv6?sendto