Литмир - Электронная Библиотека
Содержание  
A
A

Листинг 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
, но он несет в себе неявную ошибку, типичную для сетевых приложений. Мы показали один некорректный и три корректных способа решения этой проблемы:

226
{"b":"225366","o":1}