UNIX: разработка сетевых приложений
Когда мы после внесения этих изменений наберем первую строку ввода, эта строка будет отправлена как широковещательное сообщение, а мы установим аргумент функции
равным 1 с. Мы блокируемся в вызове функцииalarm, а затем для нашего сокета приходит первый ответ, вероятно, в течение нескольких миллисекунд. Ответ возвращается функциейrecvfrom, но затем мы входим в спящее состояние на одну секунду. Принимаются остальные ответы и помещаются в приемный буфер сокета. Но пока мы находимся в спящем состоянии, время таймераrecvfromистекает и генерируется сигналalarm. При этом вызывается наш обработчик сигнала, затем он возвращает управление и прерывает функциюSIGALRM, в которой мы блокированы. Далее мы повторяем цикл и читаем установленные в очередь ответы с паузой в одну секунду каждый раз, когда выводится ответ. Прочитав все ответы, мы снова блокируемся в вызове функцииsleep, однако таймер уже не работает. Мы окажемся навсегда заблокированы в вызове функцииrecvfrom. Фундаментальная проблема здесь в том, что наша цель — обеспечить прерывание блокирования в функцииrecvfromобработчиком сигнала, однако сигнал может быть доставлен в любое время, и наша программа в момент доставки сигнала может находиться в любом месте бесконечного циклаrecvfrom.forТеперь мы проанализируем четыре различных варианта решения этой проблемы: одно некорректное и три различных корректных решения.
Блокирование и разблокирование сигналаНаше первое (некорректное) решение снижает вероятность появления ошибки, блокируя сигнал и предотвращая его доставку, пока наша программа выполняет оставшуюся часть цикла
. Эта версия представлена в листинге 20.2.forЛистинг 20.2. Блокирование сигналов при выполнении в цикле for (некорректное решение)
//bcast/dgclibcast3.c1 #include "unp.h"2 static void recvfrom_alarm(int);3 void4 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)5 {6 int n;7 const int on = 1;8 char sendline[MAXLINE], recvline[MAXLINE + 1];9 sigset_t sigset_alrm;10 socklen_t len;11 struct sockaddr *preply_addr;12 preply_addr = Malloc(servlen);13 Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));14 Sigemptyset(&sigset_alrm);15 Sigaddset(&sigset_alrm, SIGALRM);16 Signal(SIGALRM, recvfrom_alarm);17 while (Fgets(sendline, MAXLINE, fp) != NULL) {18 Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);19 alarm(5);20 for (;;) {21 len = servlen;22 Sigprocmask(SIG_UNBLOCK, &sigset_alrm, NULL);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 else29 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 void40 recvfrom_alarm(int signo)41 {42 return; /* выход из recvfrom() */43 }Объявление набора сигналов и инициализацияМы объявляем набор сигналов, инициализируем его как пустой набор (14-15) и включаем бит, соответствующий сигналуsigemptyset(SIGALRM).sigaddsetРазблокирование и блокирование сигналаПеред вызовом функции21-24мы разблокируем сигнал (с тем, чтобы он мог быть доставлен, пока наша программа блокирована), а затем блокируем его, как только завершается функцияrecvfrom. Если сигнал генерируется (истекает время таймера), когда сигнал блокирован, то ядро запоминает этот факт, но доставить сигнал (то есть вызвать наш обработчик) не может, пока сигнал не будет разблокирован. В этом состоит принципиальная разница между генерацией сигнала и его доставкой. В главе 10 [110] предоставлена более подробная информация обо всех аспектах обработки сигналов POSIX.recvfromЕсли мы откомпилируем и запустим эту программу, нам будет казаться, что она работает нормально, но все программы, порождающие ситуацию гонок, большую часть времени работают без каких-либо проблем! Проблема остается: разблокирование сигнала, вызов функции
и блокирование сигнала — все эти действия являются независимыми системными вызовами. Будем считать, что функцияrecvfromвозвращает последний ответ на нашу дейтаграмму, а сигнал доставляется между вызовом функцииrecvfromи блокированием сигнала. Следующий вызов функцииrecvfromзаблокируется навсегда. Мы ограничили размер окна, но проблема осталась.recvfromВариантом решения может быть установка глобального флага при доставке сигнала его обработчиком: