, мы можем легко установить время ожидания ответа от эхо-сервера. Задаем его равным 5 с, открываем оба дескриптора для чтения и вызываем функцию
select
. Если происходит превышение времени, выводится соответствующее сообщение и осуществляется переход в начало цикла.
Вывод ответа сервера
34-38
Если дейтаграмма возвращается сервером, она выводится в стандартный поток вывода.
Обработка ICMP-ошибки
39-48
Если наше доменное соединение Unix с демоном
icmpd
готово для чтения, мы пытаемся прочитать структуру
icmpd_err
. Если это удается, выводится соответствующая информация, возвращаемая демоном.
ПРИМЕЧАНИЕ
Функция strerror является примером простой, почти тривиальной функции, которая должна быть более переносимой, чем она есть. В ANSI С ничего не говорится об ошибках, возвращаемых этой функцией. В руководстве по операционной системе Solaris 2.5 говорится, что функция возвращает пустой указатель, если ее аргумент выходит за пределы допустимых значений. Это означает, что код наподобие следующего:
printf("%s", strerror(arg));
является некорректным, поскольку strerror может вернуть пустой указатель. Однако реализации FreeBSD, так же как и все реализации исходного кода, которые автор смог найти, обрабатывают неправильный аргумент, возвращая указатель на строку типа «Неизвестная ошибка». Это имеет смысл и означает, что приведенный выше код правильный. POSIX изменил ситуацию, утверждая, что поскольку не предусмотрено значение, сигнализирующее об ошибке, связанной с выходом аргумента за допустимые пределы, функция присваивает переменной errno значение EIVAL. (Ничего не сказано об указателе, возвращаемом в случае ошибки.) Это означает, что полностью правильный код должен обнулить errno, вызвать функцию strerror, проверить, не равняется ли значение errno величине EINVAL, и в случае ошибки вывести некоторое сообщение.
Примеры эхо-клиента UDP
Приведем несколько примеров работы данного клиента, прежде чем рассматривать исходный код демона. Сначала посылаем дейтаграмму на IP-адрес, не связанный с Интернетом:
freebsd % udpcli01 192.0.2.5 echo
hi there
socket timeout
and hello
socket timeout
Мы считаем, что демон
icmpd
запущен, и ждем возвращения каким-либо маршрутизатором ICMP-ошибок недоступности получателя. Вместо этого наше приложение завершается по превышению времени ожидания. Мы показываем это, чтобы повторить, что время ожидания все еще необходимо, а генерация ICMP-
сообщения о недоступности узла может и не произойти.
В следующем примере дейтаграмма отправляется на порт стандартного эхо- сервера узла, на котором этот сервер не запущен. Мы получаем ожидаемое ICMPv4-сообщение о недоступности порта.
freebsd % udpcli01 aix-4 echo
hello
ICMP error: dest = 192.168.42.2:7. Connection refused, type = 3, code = 1
Выполнив ту же попытку с протоколом IPv6, мы получаем ICMPv6-сообщение о недоступности порта.
freebsd % udpcli01 aix-6 echo hello, world
ICMP error: dest = [3ffe:b80:1f8d:2:204:acff:fe17:bf38]:7. Connection refused, type = 1. code = 4
Демон icmpd
Начинаем описание нашего демона
icmpd
с заголовочного файла
icmpd.h
, приведенного в листинге 28.23.
Листинг 28.23. Заголовочный файл icmpd.h для демона icmpd
//icmpd/icmpd.h
1 #include "unpicmpd.h"
2 struct client {
3 int connfd; /* потоковый доменный сокет Unix к клиенту */
4 int family; /* AF_INET или AF_INET6 */
5 int lport; /* локальный порт, связанный с UDP-сокетом клиента */
6 /* сетевой порядок байтов */
7 } client[FD_SETSIZE];
8 /* глобальные переменные */
9 int fd4, fd6, listenfd, maxi, maxfd, nready;
10 fd_set rset, allset;
11 struct sockaddr_un cliaddr;
12 /* прототипы функций */
13 int readable_conn(int);
14 int readable_listen(void);
15 int readable_v4(void);
16 int readable_v6(void);
Массив client
2-17
Поскольку демон может обрабатывать любое количество клиентов, для сохранения информации о каждом клиенте используется массив структур
client
. Они аналогичны структурам данных, которые использовались в разделе 6.8. Кроме дескриптора для доменного сокета Unix, через который осуществляется связь с клиентом, сохраняется также семейство адресов клиентского UDP-сокета
AF_INET
или
AF_INET6
и номер порта, связанного с сокетом. Далее объявляются прототипы функций и глобальные переменные, совместно используемые этими функциями.
В листинге 28.24 приведена первая часть функции main.
Листинг 28.24. Первая часть функции main: создание сокетов