Категории
Самые читаемые
PochitayKnigi » Компьютеры и Интернет » Программное обеспечение » UNIX: разработка сетевых приложений - Уильям Стивенс

UNIX: разработка сетевых приложений - Уильям Стивенс

Читать онлайн UNIX: разработка сетевых приложений - Уильям Стивенс

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 184 185 186 187 188 189 190 191 192 ... 263
Перейти на страницу:

Рис. 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, обычно не буферизуются, то есть не помещаются в очередь на доставку.

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

1 ... 184 185 186 187 188 189 190 191 192 ... 263
Перейти на страницу:
Тут вы можете бесплатно читать книгу UNIX: разработка сетевых приложений - Уильям Стивенс.
Комментарии