23 n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
24 Sigprocmask(SIG_BLOCK, &sigset_alrm, NULL);
25 if (n < 0) {
26 if (errno == EINTR)
27 break; /* окончание ожидания ответа */
28 else
29 err_sys("recvfrom error");
30 } else {
31 recvline[n] = 0; /* завершающий нуль */
32 printf("from %s: %s",
33 Sock_ntop_host(preply_addr, len), recvline);
34 }
35 }
36 }
37 free(preply_addr);
38 }
39 static void
40 recvfrom_alarm(int signo)
41 {
42 return; /* выход из recvfrom */
43 }
Объявление набора сигналов и инициализация
14-15
Мы объявляем набор сигналов, инициализируем его как пустой набор (
sigemptyset
) и включаем бит, соответствующий сигналу
SIGALRM
(
sigaddset
).
Разблокирование и блокирование сигнала
21-24
Перед вызовом функции
recvfrom
мы разблокируем сигнал (с тем, чтобы он мог быть доставлен, пока наша программа блокирована), а затем блокируем его, как только завершается функция
recvfrom
. Если сигнал генерируется (истекает время таймера), когда сигнал блокирован, то ядро запоминает этот факт, но доставить сигнал (то есть вызвать наш обработчик) не может, пока сигнал не будет разблокирован. В этом состоит принципиальная разница между генерациейсигнала и его доставкой. В главе 10 [110] предоставлена более подробная информация обо всех аспектах обработки сигналов POSIX.
Если мы откомпилируем и запустим эту программу, нам будет казаться, что она работает нормально, но все программы, порождающие ситуацию гонок, большую часть времени работают без каких-либо проблем! Проблема остается: разблокирование сигнала, вызов функции
recvfrom
и блокирование сигнала — все эти действия являются независимыми системными вызовами. Будем считать, что функция
recvfrom
возвращает последний ответ на нашу дейтаграмму, а сигнал доставляется между вызовом функции
recvfrom
и блокированием сигнала. Следующий вызов функции
recvfrom
заблокируется навсегда. Мы ограничили размер окна, но проблема осталась.
Вариантом решения может быть установка глобального флага при доставке сигнала его обработчиком:
recvfrom_alarm(int signo) {
had_alarm = 1;
return;
}
Флаг сбрасывается в 0 каждый раз, когда вызывается функция
alarm
. Наша функция
dg_cli
проверяет этот флаг перед вызовом функции
recvfrom
и не вызывает ее, если флаг ненулевой.
for (;;) {
len = servlen;
Sigprocmask(SIG_UNBLOCK, &sigset_alrm, NULL);
if (had_alarm == 1)
break;
n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
Если сигнал был сгенерирован во время его блокирования (после предыдущего возвращения из функции
recvfrom
), то после разблокирования в этой части кода он будет доставлен перед завершением функции
sigprocmask
, устанавливающей наш флаг. Однако между проверкой флага и вызовом функции
recvfrom
существует промежуток времени, в течение которого сигнал может быть сгенерирован и доставлен, и если это произойдет, вызов функции
recvfrom
заблокируется навсегда (разумеется, мы считаем при этом, что не приходит никаких дополнительных ответов).
Блокирование и разблокирование сигнала с помощью функции pselect
Одним из корректных решений будет использование функции
pselect
(см. раздел 6.9), как показано в листинге 20.3.
Листинг 20.3. Блокирование и разблокирование сигналов с помощью функции pselect
//bcast/dgclibcast4.с
1 #include "unp.h"
2 static void recvfrom_alarm(int);
3 void
4 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)