Чтение онлайн

ЖАНРЫ

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-сервер, использующий сигнал

SIGIO
для получения приходящих дейтаграмм. Этот пример также иллюстрирует использование надежных сигналов стандарта POSIX.

В данном случае клиент совсем не изменен по сравнению с листингами 8.3 и 8.4, а функция сервера main не изменилась по сравнению с листингом 8.1. Единственные внесенные изменения касаются функции

dg_echo
, которая будет приведена в следующих четырех листингах. В листинге 25.1 [1] представлены глобальные объявления.

1

Все исходные коды программ, опубликованные в этой книге, вы можете найти по адресу http://www.piter.com.

Листинг 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]; /* очередь дейтаграмм для обработки */

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
, который интерпретируется как кольцевой буфер. Каждая структура содержит указатель на принятую дейтаграмму, ее длину и указатель на структуру адреса сокета, содержащую адрес протокола клиента и размер адреса протокола. В памяти размещается столько этих структур, сколько указано в
QSIZE
(в данном случае 8), и в листинге 25.2 будет видно, что функция
dg_echo
для размещения в памяти всех структур дейтаграмм и адресов сокетов вызывает функцию
malloc
. Также происходит выделение памяти под диагностический счетчик
cntread
, который будет рассмотрен чуть ниже. На рис. 25.2 приведен массив структур, при этом предполагается, что первый элемент указывает на 150-байтовую дейтаграмму, а длина связанного с ней адреса сокета равна 16.

Рис. 25.2. Структуры данных, используемые для хранения прибывающих дейтаграмм и структур адресов их сокетов

Индексы массивов

13-15
Переменная
iget
является индексом следующего элемента массива для обработки в основном цикле, а переменная
iput
— это индекс следующего элемента массива, в котором сохраняется результат действия обработчика сигнала. Переменная
nqueue
обозначает полное количество дейтаграмм, предназначенных для обработки в основном цикле.

В листинге 25.2 показан основной цикл сервера — функция

dg_echo
.

Листинг 25.2. Функция dg_echo: основной обрабатывающий цикл сервера

//sigio/dgecho01.c

19 void

20 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;

33 Signal(SIGHUP, sig_hup);

34 Signal(SIGIO, sig_io);

35 Fcntl(sockfd, F_SETOWN, getpid);

36 Ioctl(sockfd, FIOASYNC, &on);

37 Ioctl(sockfd. FIONBIO, &on);

38 Sigemptyset(&zeromask); /* инициализация трех наборов сигналов */

39 Sigemptyset(&oldmask);

40 Sigemptyset(&newmask);

Поделиться с друзьями: