Добавить в цитаты Настройки чтения

Страница 336 из 399

Листинг 29.6. Функция test_udp: отправка запросов и считывание ответов

//udpcksum/udpcksum.c

12 void

13 test_udp(void)

14 {

15  volatile int nsent = 0, timeout = 3;

16  struct udpiphdr *ui;

17  Signal(SIGALRM, sig_alrm);

18  if (sigsetjmp(jmpbuf, 1)) {

19   if (nsent >= 3)

20    err_quit("no response");

21   printf("timeoutn");

22   timeout *= 2; /* геометрическая прогрессия: 3, 6, 12 */

23  }

24  canjump = 1; /* siglongjmp разрешен */

25  send_dns_query();

26  nsent++;

27  alarm(timeout);

28  ui = udp_read();

29  canjump = 0;

30  alarm(0);

31  if (ui->ui_sum == 0)

32   printf("UDP checksums offn");

33  else

34   printf("UDP checksums o

35  if (verbose)

36   printf("received UDP checksum = %xn", ntohs(ui->ui_sum));

37 }

15 Нам нужно, чтобы две динамические локальные переменные nsent и timeout сохраняли свои значения после возвращения siglongjmp из обработчика сигнала в нашу функцию. Реализация допускает восстановление значений динамических локальных переменных, предшествовавших вызову функции sigsetjump [110, с. 178], но добавление спецификатора volatile предотвращает это восстановление.

15-16 Для сигнала SIGALRM устанавливается обработчик сигнала, а функция sigsetjmp устанавливает буфер перехода для функции siglongjmp. (Эти две функции подробно описаны в разделе 10.15 [110].) Значение 1 во втором аргументе функции sigsetjmp указывает, что требуется сохранить текущую маску сигнала, так как мы будем вызывать функцию siglongjmp из нашего обработчика сигнала.

19-23 Этот фрагмент кода выполняется, только когда функция siglongjmp вызывается из нашего обработчика сигнала. Вызов указывает на возникновение условий, при которых мы входим в состояние ожидания: мы отправили запрос, на который не пришло никакого ответа. Если после того, как мы отправим три запроса, ответа не будет, мы прекращаем выполнение кода. По истечении времени ожидания, отведенного на получение ответа, мы выводим соответствующее сообщение и увеличиваем значение времени ожидания в два раза, то есть задаем экспоненциальное смещение (exponential backoff), которое также описано в разделе 20.5. Первое значение времени ожидания равно 3 с, затем — 6 с и 12 с.

Причина, по которой в этом примере мы используем функции sigsetjmp и siglongjmp, вместо того чтобы просто перехватывать ошибку EINTR (как мы поступили в листинге 14.1), заключается в том, что библиотечные функции захвата пакетов (которые вызываются из нашей функции udp_read) заново запускают операцию чтения в случае возвращения ошибки EINTR. Поскольку мы не хотим модифицировать библиотечные функции, единственным решением для нас является перехватывание сигнала SIGALRM и выполнение нелокального перехода (оператора goto), который возвращает управление в наш код, а не в библиотечную функцию.

25-26 Функция send_dns_query (см. листинг 29.8) отправляет запрос DNS на сервер имен. Функция dns_read считывает ответ. Мы вызываем функцию alarm для предотвращения «вечной» блокировки функции read. Если истекает заданное (в секундах) время ожидания, генерируется сигнал SIGALRM, и наш обработчик сигнала вызывает функцию siglongjmp.

27-32 Если значение полученной контрольной суммы UDP равно нулю, это значит, что сервер не вычислил и не отправил контрольную сумму.

В листинге 29.7 показана наша функция sig_alrm — обработчик сигнала SIGALRM.

Листинг 29.7. Функция sig_alrm: обработка сигнала SIGALRM

//udpcksum/udpcksum.c

 1 #include "udpcksum.h"

 2 #include <setjmp.h>

 3 static sigjmp_buf jmpbuf;

 4 static int canjump;

 5 void

 6 sig_alrm(int signo)

 7 {

 8  if (canjump == 0)

 9   return;

10  siglongjmp(jmpbuf, 1);

11 }

8-10 Флаг canjump был установлен в листинге 29.6 после инициализации буфера перехода функцией sigsetjmp. Если флаг был установлен, в результате вызова функции siglongjmp управление осуществляется таким образом, как если бы функция sigsetjmp из листинга 29.6 возвратила бы значение 1.

В листинге 29.8 показана функция send_dns_query, посылающая запрос UDP на сервер DNS. Эта функция формирует запрос DNS.