UNIX: разработка сетевых приложений — страница 27 из 42

Управляемый сигналом ввод-вывод

25.1. Введение

Ввод-вывод, управляемый сигналом, подразумевает, что мы указываем ядру проинформировать нас сигналом, если что-либо произойдет с дескриптором. Исторически такой ввод-вывод назвали асинхронным вводом-выводом, но в действительности описанный далее управляемый сигналом ввод-вывод асинхронным не является. Последний обычно определяется как операция ввода-вывода с немедленным возвратом управления процессу после инициирования операции в ядре. Процесс продолжает выполняться во время того, как производится ввод-вывод. Когда операция ввода-вывода завершается или обнаруживается некоторая ошибка, процесс некоторым образом оповещается. В разделе 6.2 проводилось сравнение всех возможных типов ввода-вывода и было показано различие между вводом-выводом, управляемым сигналом, и асинхронным вводом-выводом.

Следует отметить, что неблокируемый ввод-вывод, описанный в главе 16, также не является асинхронным. При неблокируемом вводе-выводе ядро не возвращает управление после инициирования операции ввода-вывода. Управление возвращается немедленно, только если операция не может быть выполнена без блокирования процесса.

ПРИМЕЧАНИЕ

Стандарт POSIX обеспечивает истинный асинхронный ввод-вывод с помощью функций aio_XXX. Эти функции позволяют процессу решить, генерировать ли при завершении ввода-вывода сигнал, и какой именно.

Беркли-реализации поддерживают ввод-вывод, управляемый сигналом, для сокетов и устройств вывода с помощью сигнала

SIGIO
. SVR4 поддерживает ввод- вывод, управляемый сигналом, для устройств STREAMS с помощью сигнала
SIGPOLL
, который в данном случае приравнивается к
SIGIO
.

25.2. Управляемый сигналом ввод-вывод для сокетов

Для использования управляемого сигналом ввода-вывода с сокетом (

SIGIO
) необходимо, чтобы процесс выполнил три следующих действия:

1. Установил обработчик сигнала

SIGIO
.

2. Задал владельца сокета. Обычно это выполняется с помощью команды

F_SETOWN
функции
fcntl
(см. табл. 7.9).

3. Разрешил управляемый сигналом ввод-вывод для данного сокета, что обычно выполняется с помощью команды

F_SETFL
функции
fcntl
или путем включения флага
O_ASYNC
(см. табл. 7.9).

ПРИМЕЧАНИЕ

Флаг O_ASYNC был добавлен в POSIX относительно поздно. Его поддержка пока реализована в небольшом количестве систем. Для разрешения управляемого сигналом ввода-вывода в листинге 25.2 вместо этого флага мы используем функцию ioctl с флагом FIOASYNC. Следует отметить, что разработчики POSIX выбрали не самое удачное имя для нового флага: ему больше подходит имя O_SIGIO.

Обработчик сигнала должен быть установлен до того, как будет задан владелец сокета. В Беркли-реализациях порядок вызова этих функций не имеет значения, поскольку по умолчанию сигнал SIGIO игнорируется. Поэтому если изменить порядок вызова функций на противоположный, появится небольшая вероятность того, что сигнал будет сгенерирован после вызова функции fcntl, но перед вызовом функции signal. Однако если это произойдет, то сигнал просто не будет учитываться. В SVR4 SIGIO определяется в заголовочном файле как SIGPOLL, а действием по умолчанию для SIGPOLL является прерывание процесса. Таким образом, в SVR4 желательно быть уверенным в том, что обработчик сигнала установлен до задания владельца сокета.

Перевести сокет в режим ввода-вывода, управляемого сигналом, несложно. Сложнее определить условия, которые должны приводить к генерации сигнала

SIGIO
для владельца сокета. Это зависит от транспортного протокола.

Сигнал SIGIO и сокеты UDP

Использовать ввод-вывод, управляемый сигналом, с сокетами UDP довольно легко. Сигнал генерируется в следующих случаях:

■ на сокет прибывает дейтаграмма;

■ на сокете возникает асинхронная ошибка.

Таким образом, когда мы перехватываем сигнал

SIGIO
для сокета UDP, вызывается функция
recvfrom
как для чтения дейтаграммы, так и для получения асинхронной ошибки. Асинхронные ошибки, касающиеся UDP-сокетов, обсуждались в разделе 8.9. Напомним, что эти сигналы генерируются, только если сокет UDP является присоединенным (создан с помощью вызова функции
connect
).

ПРИМЕЧАНИЕ

Сигнал SIGIO генерируется для этих двух условий путем вызова макроса sorwakeup, описываемого в книге [128, с. 775, с. 779, с. 784].

Сигнал SIGIO и сокеты TCP

К сожалению, использовать управляемый сигналом ввод-вывод для сокетов TCP почти бесполезно. Проблема состоит в том, что сигнал генерируется слишком часто, а само по себе возникновение сигнала не позволяет выяснить, что произошло. Как отмечается в [128, с. 439], генерацию сигнала

SIGIO
для TCP-сокета вызывают все нижеперечисленные ситуации (при условии, что управляемый сигналом ввод-вывод разрешен):

■ на прослушиваемом сокете выполнен запрос на соединение;

■ инициирован запрос на отключение;

■ запрос на отключение выполнен;

■ половина соединения закрыта;

■ данные доставлены на сокет;

■ данные отправлены с сокета (то есть в буфере отправки имеется свободное место);

■ произошла асинхронная ошибка.

Например, если одновременно осуществляются и чтение, и запись в TCP-сокет, то сигнал

SIGIO
генерируется, когда поступают новые данные и когда подтверждается прием ранее записанных данных, а обработчик сигнала не имеет возможности различить эти сигналы. Если используется сигнал
SIGIO
, то для предотвращения блокирования при выполнении функции
read
или
write
TCP-сокет должен находиться в режиме неблокируемого ввода-вывода. Следует использовать сигнал
SIGIO
лишь с прослушиваемым сокетом TCP, поскольку для прослушиваемого сокета этот сигнал генерируется только при завершении установления нового соединения.

Единственное реальное применение управляемого сигналом ввода-вывода с сокетами, которое удалось обнаружить автору, — это сервер 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]; /* очередь дейтаграмм для обработки */

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

41  Sigaddset(&newmask, SIGIO); /* сигнал, который хотим блокировать*/


42  Sigprocmask(SIG_BLOCK, &newmask, &oldmask);

43  for (;;) {

44   while (nqueue == 0)

45    sigsuspend(&zeromask); /* ждем дейтаграмму для обработки */


46   /* разблокирование SIGIO */

47   Sigprocmask(SIG_SETMASK, &oldmask, NULL);


48   Sendto(sockfd, dg[iget].dg_data, dg[iget].dg_len, 0,

49    dg[iget].dg_sa, dg[iget].dg_salen);


50   if (++iget >= QSIZE)

51    iget = 0;


52   /* блокировка SIGIO */

53   Sigprocmask(SIG_BLOCK, &newmask, &oldmask);

54   nqueue--;

55  }

56 }

Инициализация очереди принятых дейтаграмм

27-32
 Дескриптор сокета сохраняется в глобальной переменной, поскольку он необходим обработчику сигналов. Происходит инициализация очереди принятых дейтаграмм.

Установка обработчиков сигналов и флагов сокетов

33-37
 Для сигналов
SIGHUP
(он используется для диагностических целей) и
SIGIO
устанавливаются обработчики. С помощью функции
fcntl
задается владелец сокета, а с помощью функции
ioctl
устанавливаются флаги ввода-вывода, управляемого сигналом, и неблокируемого ввода-вывода.

ПРИМЕЧАНИЕ

Ранее отмечалось, что для разрешения ввода-вывода, управляемого сигналом, в POSIX применяется флаг O_ASYNC функции fcntl, но поскольку большинство систем пока его не поддерживают, мы используем функцию ioctl. Поскольку большинство систем не поддерживают флаг O_NONBLOCK для включения неблокируемого ввода-вывода, здесь также рассмотрен вариант использования функции ioctl.

Инициализация наборов сигналов

38-41
 Инициализируется три набора сигналов:
zeromask
(никогда не изменяется),
oldmask
(хранит старую маску сигнала, когда
SIGIO
блокируется) и
newmask
. Функция
sigaddset
включает в набор
newmask
бит, соответствующий
SIGIO
.

Блокирование SIGIO и ожидание дальнейших действий

42-45
 Функция
sigprocmask
сохраняет текущую маску сигналов процесса в
oldmask
, а затем выполняет логическое сложение, сравнивая
newmask
с текущей маской сигналов. Такие действия блокируют сигнал
SIGIO
и возвращают текущую маску сигналов. Далее мы заходим в цикл
for
и проверяем счетчик
nqueue
. Пока этот счетчик равен нулю, ничего делать не нужно, и мы вызываем функцию
sigsuspend
. Эта функция POSIX, сохранив в одной из локальных переменных текущую маску сигналов, присваивает текущей маске значение аргумента
zeromask
. Так как
zeromask
является пустым набором сигналов, то разрешается доставка любых сигналов. Как только перехватывается сигнал и завершается обработчик, функция
sigsuspend
также завершается. (Это необычная функция, поскольку она всегда возвращает ошибку
EINTR
.) Прежде чем завершиться, функция
sigsuspend
всегда устанавливает такое значение маски сигналов, которое предшествовало ее вызову (в данном случае
newmask
). Таким образом гарантируется, что, когда функция
sigsuspend
возвращает значение, сигнал
SIGIO
блокирован. Именно поэтому можно проверять счетчик
nqueue
, поскольку известно, что пока он проверяется, сигнал
SIGIO
не может быть доставлен.

ПРИМЕЧАНИЕ

А что произойдет, если сигнал SIGIO не будет блокирован во время проверки переменной nqueue, используемой совместно основным циклом и обработчиком сигналов? Может случиться так, что проверка nqueue покажет нулевое значение, а сразу после проверки возникнет сигнал и nqueue станет равна 1. Далее мы вызовем функцию sigsuspend и перейдем в режим ожидания, в результате чего пропустим сигнал. После вызова функции sigsuspend мы не выйдем из режима ожидания, пока не поступит другой сигнал. Это похоже на ситуацию гонок, описанную в разделе 20.5

Разблокирование SIGIO и отправка ответа

46-51
 Разблокируем сигнал SIGIO с помощью вызова
sigprocmask
, чтобы вернуть маске сигналов процесса значение, сохраненное ранее (
oldmask
). В этом случае ответ посылается с помощью функции
sendto
. Индекс
iget
увеличился на 1, и если его значение совпадает с количеством элементов массива, он снова обнуляется. Массив используется как кольцевой буфер. Обратите внимание, что нет необходимости блокировать сигнал
SIGIO
во время изменения переменной
iget
, поскольку этот индекс используется только в основном цикле и никогда не изменяется обработчиком сигнала.

Блокирование SIGIO

52-54
 Сигнал
SIGIO
блокируется, а значение переменной
nqueue
уменьшается на 1. Во время изменения данной переменной необходимо заблокировать сигнал, поскольку она используется совместно основным циклом и обработчиком сигнала. Также необходимо, чтобы сигнал
SIGIO
был заблокирован, когда в начале цикла происходит проверка переменной
nqueue
.

Альтернативным способом является удаление обоих вызовов функции

sigprocmask
, находящихся внутри цикла
for
, что предотвращает разблокирование сигнала и его последующее блокирование. Однако проблема состоит в следующем: в такой ситуации весь цикл выполняется при блокированном сигнале, что уменьшает быстроту реагирования обработчика сигнала. При этом дейтаграммы не будут теряться (если, конечно, буфер приема сокета достаточно велик), но выдача сигнала процессу будет задерживаться на время блокирования сигнала. Одной из задач при создании приложений, производящих обработку сигналов, должна быть минимизация времени блокирования сигнала.

Листинг 25.3. Обработчик сигнала SIGIO

//sigio/dgecho01.c

57 static void

58 sig_io(int signo)

59 {

60  ssize_t len;

61  int nread;

62  DG *ptr;

63  for (nread = 0;;) {

64   if (nqueue >= QSIZE)

65    err_quit("receive overflow");


66   ptr = &dg[iput];

67   ptr->dg_salen = clilen;

68   len = recvfrom(sockfd, ptr->dg_data, MAXDG, 0,

69    ptr->dg_sa, &ptr->dg_salen);

70   if (len < 0) {

71    if (errno == EWOULDBLOCK)

72     break; /* все сделано; очередь на чтение отсутствует */

73    else

74     err_sys("recvfrom error");

75   }

76   ptr->dg_len = len;


77   nread++;

78   nqueue++;

79   if (++iput >= QSIZE)

80    iput = 0;


81  }

82  cntread[nread]++; /* гистограмма количества дейтаграмм.

                         считанных для каждого сигнала */

83 }

Во время создания этих обработчиков сигналов была обнаружена следующая проблема: в стандарте POSIX сигналы обычно не помещаются в очередь. Это означает, что если во время пребывания внутри обработчика сигналов (при этом сигнал заведомо заблокирован) возникает еще два сигнала, то сигнал доставляется еще один раз.

ПРИМЕЧАНИЕ

В стандарте POSIX предусмотрено несколько сигналов реального времени, для которых обеспечивается буферизация, однако ряд других сигналов, в том числе и SIGIO, обычно не буферизуются, то есть не помещаются в очередь на доставку.

Рассмотрим следующий сценарий. Прибывает дейтаграмма и выдается сигнал. Обработчик сигнала считывает дейтаграмму и помещает ее в очередь к основному циклу. Но во время работы обработчика сигнала приходят еще две дейтаграммы, вызывая генерацию сигнала еще дважды. Поскольку сигнал блокирован, то когда обработчик сигналов возвращает управление после обработки первого сигнала, он запустится снова всего лишь один раз. После второго запуска обработчик считывает вторую дейтаграмму, а третья будет оставлена в очереди приходящих дейтаграмм сокета. Эта третья дейтаграмма будет прочитана, только если (и только когда) придет четвертая. Когда придет четвертая дейтаграмма, считана и поставлена в очередь на обработку основным циклом будет именно третья, а не четвертая дейтаграмма.

Поскольку сигналы не помещаются в очередь, дескриптор, установленный для управляемого сигналом ввода-вывода, обычно переводится в неблокируемый режим. Обработчик сигнала

SIGIO
мы кодируем таким образом, чтобы он считывал дейтаграммы в цикле, который прерывается, только когда при считывании возвращается ошибка
EWOULDBLOCK
.

Проверка переполнения очереди

64-65
 Если очередь переполняется, происходит завершение работы. Для обработки такой ситуации существуют и другие способы (например, можно размещать в памяти дополнительные буферы), но для данного примера достаточно простого завершения.

Чтение дейтаграммы

66-76
 На неблокируемом сокете вызывается функция
recvfrom
. Элемент массива, обозначенный индексом
iput
, — это то место, куда записывается дейтаграмма. Если нет дейтаграмм, которые нужно считывать, мы выходим из цикла
for
с помощью оператора
break
.

Увеличение счетчиков и индекса на единицу

77-80
 Переменная
nread
является диагностическим счетчиком количества дейтаграмм, читаемых на один сигнал. Переменная
nqueue
— это количество дейтаграмм для обработки основным циклом.

82
 Прежде чем обработчик сигналов возвращает управление, он увеличивает счетчик на единицу в соответствии с количеством дейтаграмм, прочитанных за один сигнал. Этот массив распечатывается программой в листинге 25.4 и представляет собой диагностическую информацию для обработки сигнала
SIGHUP
.

Последняя функция (листинг 25.4) представляет собой обработчик сигнала

SIGHUP
, который выводит массив
cntread
. Он считает количество дейтаграмм, прочитанных за один сигнал.

Листинг 25.4. Обработчик сигнала SIGHUP

//sigio/dgecho01.c

84 static void

85 sig_hup(int signo)

86 {

87  int i;


88  for (i = 0; i <= QSIZE; i++)

89   printf("cntread[%d] = %ld\n", i, cntread[i]);

90 }

Чтобы проиллюстрировать, что сигналы не буферизуются и что в дополнение к установке флага, указывающего на управляемый сигналом ввод-вывод, необходимо перевести сокет в неблокируемый режим, запустим этот сервер с шестью клиентами одновременно. Каждый клиент посылает серверу 3645 строк (для отражения). При этом каждый клиент запускается из сценария интерпретатора в фоновом режиме, так что все клиенты стартуют приблизительно одновременно. Когда все клиенты завершены, серверу посылается сигнал

SIGHUP
, в результате чего сервер выводит получившийся массив
cntread
:

linux % udpserv01

cntread[0] = 2

cntread[1] = 21838

cntread[2] = 12

cntread[3] = 1

cntread[4] = 0

cntread[5] = 1

cntread[6] = 0

cntread[7] = 0

cntread[8] = 0

Большую часть времени обработчик сигналов читает только одну дейтаграмму, но бывает, что готово больше одной дейтаграммы. Ненулевое значение счетчика

cntread[0]
получается потому, что сигнал генерируется в процессе выполнения клиента. Мы считываем дейтаграммы в цикле обработчика сигнала. Дейтаграмма, прибывшая во время считывания других дейтаграмм, будет считана вместе с этими дейтаграммами (в том же вызове обработчика), а сигнал об ее прибытии будет отложен и доставлен процессу после завершения обработчика. Это приведет к повторному вызову обработчика, но считывать ему будет нечего (отсюда
cntread[0]>0
). Наконец, можно проверить, что взвешенная сумма элементов массива (21 838×1 + 12×2 + 1×3+1×5=21 870) равна 6×3645 (количество клиентов × количество строк клиента).

25.4. Резюме

При управляемом сигналом вводе-выводе ядро уведомляет процесс сигналом

SIGIO
, если «что-нибудь» происходит на сокете.

■ Для присоединенного TCP-сокета существует множество ситуаций, которые вызывают такое уведомление, что делает эту возможность практически бесполезной.

■ Для прослушиваемого TCP-сокета уведомление приходит процессу только в случае готовности принятия нового соединения.

■ Для UDP такое уведомление означает, что либо пришла дейтаграмма, либо произошла асинхронная ошибка: в обоих случаях вызывается

recvfrom
.

С помощью метода, аналогичного применяемому для сервера NTP, был изменен эхо-сервер UDP для работы с вводом-выводом, управляемым сигналом: мы стремимся выполнить чтение дейтаграммы как можно быстрее после ее прибытия, чтобы получить точную отметку времени прибытия и поставить дейтаграмму в очередь для дальнейшей обработки.

Упражнения

1. Далее приведен альтернативный вариант цикла, рассмотренного в листинге 25.2:

for (;;) {

 Sigprocmask(SIG_BLOCK, &newmask, &oldmask);

 while (nqueue == 0)

  sigsuspend(&zeromask); /* ожидание дейтаграммы для обработки */

 nqueue--;


 /* разблокирование SIGIO */

 Sigprocmask(SIG_SETMASK, &oldmask, NULL);


 Sendto(sockfd, dg[iget].dg_data, dg[iget].dg_len, 0,

  dg[iget].dg_sa, dg[iget].dg_salen);

 if (++iget >= QSIZE)

  iget = 0;

}

Верна ли такая модификация?

Глава 26