Единственное реальное применение управляемого сигналом ввода-вывода с сокетами, которое удалось обнаружить автору, — это сервер NTP (Network Time Protocol — сетевой протокол синхронизации времени), использующий протокол UDP. Основной цикл этого сервера получает дейтаграмму от клиента и посылает ответ. Но обработка клиентского запроса на этом сервере требует некоторого ненулевого количества времени (больше, чем для нашего тривиального эхо-сервеpa). Серверу важно записать точные отметки времени для каждой принимаемой дейтаграммы, поскольку это значение возвращается клиенту и используется им для вычисления времени обращения к серверу (RTT). На рис. 25.1 показаны два варианта построения такого UDP-сервера.
Рис. 25.1. Два варианта построения UDP-сервера
Большинство UDP-серверов (включая наш эхо-сервер, описанный в главе 8) построены так, как показано на рисунке слева. Однако NTP-сервер использует способ, показанный справа: когда прибывает новая дейтаграмма, она читается обработчиком сигнала SIGIO
, который также записывает время прибытия дейтаграммы. Далее дейтаграмма помещается в другую очередь внутри процесса, из которой она будет извлечена, а затем обработана основным циклом сервера. Это усложняет код сервера, но зато обеспечивает точные отметки времени прибытия дейтаграмм.
Вспомните листинг 22.3: процесс может установить параметр сокета IP_RECVDSTADDR, чтобы получить адрес получателя пришедшей UDP-дейтаграммы. Можно возразить, что вместе с полученной дейтаграммой UDP должны быть возвращены два дополнительных фрагмента информации — интерфейс, на котором была получена дейтаграмма (этот интерфейс может отличаться от адреса получателя, если узел использует более типичную модель системы с гибкой привязкой), и время прибытия дейтаграммы.
Для IPv6 интерфейс, на котором была получена дейтаграмма, можно получить, если включен параметр сокета IPV6_PKTINFO (см. раздел 22.8). Аналогичный параметр сокета IP_RECVIF для IPv4 описывался в разделе 22.2.
В FreeBSD также предусмотрен параметр сокета SO_TIMESTAMP, возвращающий время получения дейтаграммы как вспомогательные данные в структуре timeval. В Linux существует флаг SIOCGSTAMP для функции ioctl, которая возвращает структуру timeval, содержащую время прибытия дейтаграммы.
25.3. Эхо-сервер UDP с использованием сигнала SIGIO
В этом разделе мы приведем пример, аналогичный правой части рис. 25.1: UDP-сервер, использующий сигнал SIGIO
для получения приходящих дейтаграмм. Этот пример также иллюстрирует использование надежных сигналов стандарта POSIX.
В данном случае клиент совсем не изменен по сравнению с листингами 8.3 и 8.4, а функция сервера main не изменилась по сравнению с листингом 8.1. Единственные внесенные изменения касаются функции dg_echo
, которая будет приведена в следующих четырех листингах. В листинге 25.1[1] представлены глобальные объявления.
Листинг 25.1. Глобальные объявления
//sigio/dgecho01.c
1 #include "unp.h"
2 static int sockfd;
3 #define QSIZE 8 /* размер входной очереди */
4 #define MAXDG 4096 /* максимальный размер дейтаграммы */
5 typedef struct {
6 void *dg_data; /* указатель на текущую дейтаграмму */
7 size_t dg_len; /* длина дейтаграммы */
8 struct sockaddr *dg_sa; /* указатель на sockaddr{} с адресом клиента */
9 socklen_t dg_salen; /* длина sockaddr{} */
10 } DG;
11 static DG dg[QSIZE]; /* очередь дейтаграмм для обработки */