UNIX: разработка сетевых приложений
Рис. 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-сервер, использующий сигнал
для получения приходящих дейтаграмм. Этот пример также иллюстрирует использование надежных сигналов стандарта POSIX.SIGIOВ данном случае клиент совсем не изменен по сравнению с листингами 8.3 и 8.4, а функция сервера main не изменилась по сравнению с листингом 8.1. Единственные внесенные изменения касаются функции
, которая будет приведена в следующих четырех листингах. В листинге 25.1 [1] представлены глобальные объявления.dg_echoЛистинг 25.1. Глобальные объявления
//sigio/dgecho01.c1 #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]; /* очередь дейтаграмм для обработки */12 static long cntread[QSIZE +1]; /* диагностический счетчик */13 static int iget; /* следующий элемент для обработки в основном цикле */14 static int iput; /* следующий элемент для считывания обработчикомсигналов */15 static int nqueue; /* количество дейтаграмм в очереди на обработкув основном цикле */16 static socklen_t clilen; /* максимальная длина sockaddr{} */17 static void sig_io(int);18 static void sig_hup(int);Очередь принимаемых дейтаграммОбработчик сигнала3-12помещает приходящие дейтаграммы в очередь. Эта очередь является массивом структурSIGIO, который интерпретируется как кольцевой буфер. Каждая структура содержит указатель на принятую дейтаграмму, ее длину и указатель на структуру адреса сокета, содержащую адрес протокола клиента и размер адреса протокола. В памяти размещается столько этих структур, сколько указано вDG(в данном случае 8), и в листинге 25.2 будет видно, что функцияQSIZEдля размещения в памяти всех структур дейтаграмм и адресов сокетов вызывает функциюdg_echo. Также происходит выделение памяти под диагностический счетчикmalloc, который будет рассмотрен чуть ниже. На рис. 25.2 приведен массив структур, при этом предполагается, что первый элемент указывает на 150-байтовую дейтаграмму, а длина связанного с ней адреса сокета равна 16.cntreadРис. 25.2. Структуры данных, используемые для хранения прибывающих дейтаграмм и структур адресов их сокетов
Индексы массивовПеременная13-15является индексом следующего элемента массива для обработки в основном цикле, а переменнаяiget— это индекс следующего элемента массива, в котором сохраняется результат действия обработчика сигнала. Переменнаяiputобозначает полное количество дейтаграмм, предназначенных для обработки в основном цикле.nqueueВ листинге 25.2 показан основной цикл сервера — функция
.dg_echoЛистинг 25.2. Функция dg_echo: основной обрабатывающий цикл сервера
//sigio/dgecho01.c19 void20 dg_echo(int sockfd_arg, SA *pcliaddr, socklen_t clilen_arg)21 {22 int i;23 const int on = 1;24 sigset_t zeromask, newmask, oldmask;25 sockfd = sockfd_arg;26 clilen = clilen_arg;27 for (i = 0; i < QSIZE; i++) { /* инициализация очереди */28 dg[i].dg_data = Malloc(MAXDG);29 dg[i].dg_sa = Malloc(clilen);30 dg[i].dg_salen = clilen;31 }32 iget = iput = nqueue = 0;

