Листинг 20.6 демонстрирует использование канала внутри процесса. Обработчик сигналов записывает в канал 1 байт, когда истекает время таймера, а наша функция
dg_cli
считывает этот байт, чтобы определить, когда завершить свой цикл
for
. Что замечательно в этом решении — проверка готовности канала осуществляется функцией
select
. С ее помощью мы проверяем, готов ли к считыванию сокет или канал.
Листинг 20.6. Использование канала в качестве IPC между обработчиком сигнала и нашей функцией
//bcast/dgclibcast6.c
1 #include "unp.h"
2 static void recvfrom_alarm(int);
3 static int pipefd[2];
4 void
5 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
6 {
7 int n, maxfdp1;
8 const int on = 1;
9 char sendline[MAXLINE], recvline[MAXLINE + 1];
10 fd_set rset;
11 socklen_t len;
12 struct sockaddr *preply_addr;
13 preply_addr = Malloc(servlen);
14 Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
15 Pipe(pipefd);
16 maxfdp1 = max(sockfd, pipefd[0]) + 1;
17 FD_ZERO(&rset);
18 Signal(SIGALRM, recvfrom_alarm);
19 while (Fgets(sendline, MAXLINE, fp) != NULL) {
20 Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
21 alarm(5);
22 for (;;) {
23 FD_SET(sockfd, &rset);
24 FD_SET(pipefd[0], &rset);
25 if ((n = select(maxfdp1, &rset, NULL, NULL, NULL)) < 0) {
26 if (errno == EINTR)
27 continue;
28 else
29 err_sys("select error");
30 }
31 if (FD_ISSET(sockfd, &rset)) {
32 len = servlen;
33 n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr,
34 &len);
35 recvline[n] = 0; /* null terminate */
36 printf("from %s: %s",
37 Sock_ntop_host(preply_addr, len), recvline);
38 }
39 if (FD_ISSET(pipefd[0], &rset)) {
40 Read(pipefd[0], &n, 1); /* истекшее время */
41 break;
42 }
43 }
44 }
45 free(preply_addr);
46 }
47 static void
48 recvfrom_alarm(int signo)
49 {
50 Write(pipefd[1], "", 1); /* в канал пишется один нулевой байт */
51 return;
52 }
Создание канала
15
Мы создаем обычный канал Unix. Возвращаются два дескриптора:
pipefd[0]
доступен для чтения, а
pipefd[0]
— для записи.
ПРИМЕЧАНИЕ
Мы могли бы использовать функцию socketpair и получить двусторонний канал. В некоторых системах, особенно SVR4, обычный канал Unix всегда является двусторонним, и мы можем и читать, и записывать на любом конце этого канала.
Функция select на сокете и считывающем конце канала
23-30
Мы вызываем функцию
select
и на сокете, и на считывающем конце канала.
47-52
Когда доставляется сигнал
SIGALRM
, наш обработчик сигналов записывает в канал 1 байт, в результате чего считывающий конец канала становится готовым для чтения. Наш обработчик сигнала также возвращает управление, возможно, прерывая функцию
select
. Следовательно, если функция
select
возвращает ошибку
EINTR
, мы игнорируем эту ошибку, зная, что считывающий конец канала также готов для чтения, что завершит цикл
for
.
Чтение из канала
38-41
Когда считывающий конец канала готов для чтения, мы с помощью функции read считываем нулевой байт, записанный обработчиком сигнала, и игнорируем его. Но прибытие этого нулевого байта указывает нам на то, что истекло время таймера, и мы с помощью функции
break
выходим из бесконечного цикла
for
.
20.6. Резюме
При широковещательной передаче посылается дейтаграмма, которую получают все узлы. Недостатком широковещательной передачи является то, что каждый узел в подсети должен обрабатывать дейтаграмму, вплоть до уровня UDP в случае дейтаграммы UDP, даже если на узле не выполняется приложение-адресат. Для приложений с большими потоками данных, таких как аудио- и видео-приложения, это может привести к повышенной нагрузке на все узлы. В следующей главе мы увидим, что многоадресная передача решает эту проблему, поскольку позволяет не получать дейтаграмму узлам, не заинтересованным в этом.
Использование версии нашего эхо-клиента UDP, который отправляет серверу времени и даты широковещательные дейтаграммы и затем выводит все его ответы, полученные в течение 5 с, позволяет нам рассмотреть ситуацию гонок, возникающую при применении сигнала
SIGALRM
. Общим способом помещения тайм-аута в операцию чтения является использование функции
alarm
и сигнала
SIGALRM
, но он несет в себе неявную ошибку, типичную для сетевых приложений. Мы показали один некорректный и три корректных способа решения этой проблемы: