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 всегда является двусторонним, и мы можем и читать, и записывать на любом конце этого канала.
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
, но он несет в себе неявную ошибку, типичную для сетевых приложений. Мы показали один некорректный и три корректных способа решения этой проблемы:
■ использование функции pselect
,
■ использование функций sigsetjmp
и siglongjmp
,
■ использование средств IPC (обычно канала) между обработчиком сигнала и главным циклом.
Упражнения
1. Запустите клиент UDP, используя функцию dg_cli
, выполняющую широковещательную передачу (см. листинг 20.1). Сколько ответов вы получаете? Всегда ли ответы приходят в одном и том же порядке? Синхронизированы ли часы у узлов в вашей подсети?
2. Поместите несколько функций printf
в листинг 20.6 после завершения функции select
, чтобы увидеть, возвращает ли она ошибку или указание на готовность к чтению одного из двух дескрипторов. Возвращает ли ваша система ошибку EINTR
или сообщение о готовности канала к чтению, когда истекает время таймера alarm
?
3. Запустите такую программу, как tcpdump
, если это возможно, и просмотрите широковещательные пакеты в вашей локальной сети (команда tcpdump ether broadcast
). К каким наборам протоколов относятся эти широковещательные пакеты?
Глава 21
Многоадресная передача
21.1. Введение
Как показано в табл. 20.1, адрес направленной передачи идентифицирует