31-32
select
и вычисляем максимальный из двух дескрипторов, готовности которого будем ожидать.34-41
select
, ожидая только готовности к чтению прослушиваемого сокета TCP или сокета UDP. Поскольку наш обработчик сигнала sig_chld
может прервать вызов функции select
, обрабатываем ошибку EINTR
.42-51
accept
мы принимаем новое клиентское соединение, а когда прослушиваемый сокет TCP готов для чтения, с помощью функции fork
порождаем дочерний процесс и вызываем нашу функцию str_echo
в дочернем процессе. Это та же последовательность действий, которую мы выполняли в главе 5.52-57
recvfrom
и отправляем обратно клиенту с помощью функции sendto
.8.16. Резюме
Преобразовать наши эхо-клиент и эхо-сервер так, чтобы использовать UDP вместо TCP, оказалось несложно. Но при этом мы лишились множества возможностей, предоставляемых протоколом TCP: определение потерянных пакетов и повторная передача, проверка, приходят ли пакеты от корректного собеседника, и т.д. Мы возвратимся к этой теме в разделе 22.5 и увидим, как можно улучшить надежность приложения UDP.
Сокеты UDP могут генерировать асинхронные ошибки, то есть ошибки, о которых сообщается спустя некоторое время после того, как пакет был отправлен. Сокеты TCP всегда сообщают приложению о них, но в случае UDP для получения этих ошибок сокет должен быть присоединенным.
В UDP отсутствует возможность управления потоком, что очень легко продемонстрировать. Обычно это не создает проблем, поскольку многие приложения UDP построены с использованием модели «запрос-ответ» и не предназначены для передачи большого количества данных.
Есть еще ряд моментов, которые нужно учитывать при написании приложений UDP, но мы рассмотрим их в главе 22 после описания функций интерфейсов, широковещательной и многоадресной передачи.
Упражнения
1. Допустим, у нас имеется два приложения, одно использует TCP, а другое — UDP. В приемном буфере сокета TCP находится 4096 байт данных, а в приемном буфере для сокета UDP — две дейтаграммы по 2048 байт. Приложение TCP вызывает функцию read
recvfrom
с третьим аргументом 4096. Есть ли между этими вызовами какая-нибудь разница?2. Что произойдет в листинге 8.2, если мы заменим последний аргумент функции sendto
len
) аргументом clilen
?3. Откомпилируйте и запустите сервер UDP из листингов 8.1 и 8.4, а затем — клиент из листингов 8.3 и 8.4. Убедитесь в том, что клиент и сервер работают вместе.
4. Запустите программу ping
-i 60
(отправка одного пакета каждые 60 секунд; некоторые системы используют ключ I
вместо i
), параметр -v
(вывод всех полученных сообщений об ошибках ICMP) и задав адрес закольцовки на себя (обычно 127.0.0.1). Мы будем использовать эту программу, чтобы увидеть ошибку ICMP недоступности порта, возвращаемую узлом сервера. Затем запустите наш клиент из предыдущего упражнения в другом окне, задав IP-адрес некоторого узла, на котором не запущен сервер. Что происходит?5. Рассматривая рис. 8.3, мы сказали, что каждый присоединенный сокет TCP имеет свой собственный буфер приема. Как вы думаете, есть ли у прослушиваемого сокета свой собственный буфер приема?
6. Используйте программу sock
tcpdump
(см. раздел В.5), чтобы проверить утверждение из раздела 8.10: если клиент с помощью функции bind
связывает IP-адрес со своим сокетом, но отправляет дейтаграмму, исходящую от другого интерфейса, то результирующая дейтаграмма содержит IP-адрес, который был связан с сокетом, даже если он не соответствует исходящему интерфейсу.7. Откомпилируйте программы из раздела 8.13 и запустите клиент и сервер на различных узлах. Помещайте printf
printf
из сервера каждый раз, когда дейтаграмма читается из сокета. Изменяет ли это процент полученных пакетов? Почему?8. Какова наибольшая длина, которую мы можем передать функции sendto
Измените листинг 8.4, с тем чтобы отправить одну дейтаграмму UDP максимального размера, считать ее обратно и вывести число байтов, возвращаемых функцией recvfrom
9. Измените листинг 8.15 таким образом, чтобы он соответствовал RFC 1122: для сокета UDP следует использовать параметр IP_RECVDSTADDR
Глава 9
Основы сокетов SCTP
9.1. Введение