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

16   alarm(5);

17   for (;;) {

18    len = servlen;

19    n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);

20    if (n < 0) {

21     if (errno == EINTR)

22      break; /* окончание ожидания ответов */

23     else

24      err_sys("recvfrom error");

25    } else {

26     recvline[n] = 0; /* завершающий нуль */

27     printf("from %s: %s",

28      Sock_ntop_host(preply_addr, len), recvline);

29    }

30   }

31  }

32  free(preply_addr);

33 }

34 static void

35 recvfrom_alarm(int signo)

36 {

37  return; /* прерывание recvfrom() */

38 }

Выделение памяти для адреса сервера, установка параметра сокета

11-13
 Функция
malloc
выделяет в памяти пространство для адреса сервера, возвращаемого функцией
recvfrom
. Устанавливается параметр сокета
SO_BROADCAST
, устанавливается обработчик сигнала
SIGALRM
.

Чтение строки, отправка сокету, чтение всех ответов

14-24
 Следующие два вызова,
fgets
и
sendto
, выполняются так же, как и в предыдущих версиях этой функции. Но поскольку мы посылаем широковещательную дейтаграмму, мы можем получить множество ответов. Мы вызываем в цикле функцию
recvfrom
и выводим все ответы, полученные в течение 5 с. По истечении 5 с генерируется сигнал
SIGALRM
, вызывается наш обработчик сигнала и функция
recvfrom
возвращает ошибку
EINTR
.

Вывод каждого полученного ответа

25-29
 Для каждого полученного ответа мы вызываем функцию
sock_ntop_host
, которая в случае IPv4 возвращает строку, содержащую IP-адрес сервера в точечно-десятичной записи. Эта строка выводится вместе с ответом сервера.

Если мы запустим программу, задав широковещательный адрес подсети 192. 168.42.255, мы увидим следующее:

bsdi % <b>udpcli01 192.168.42.255 hi</b>

from 192 168.42 2: Sat Aug 2 16.42.45 2003

from 192.168.42.1: Sat Aug 2 14.42.45 2003

from 192.168.42.3: Sat Aug 2 14.42.45 2003

<b>hello</b>

from 192.168.42.3: Sat Aug 2 14.42.57 2003

from 192.168.42.2: Sat Aug 2 16.42.57 2003

from 192.168.42.1: Sat Aug 2 14.42.57 2003

Каждый раз мы набираем строку ввода, чтобы сгенерировать выходную дейтаграмму UDP, и каждый раз получаем три ответа, причем отвечает и отправляющий узел. Как мы отмечали ранее, получателями широковещательной дейтаграммы являются все узлы в сети, включая отправляющий. Каждый ответ является направленным, поскольку адрес отправителя запроса, используемый каждым сервером в качестве адреса получателя ответа, — это адрес направленной передачи.

Все системы сообщают одно и то же время, поскольку на них используется NTP (Network Time Protocol — протокол синхронизации времени).

Фрагментация IP-пакетов и широковещательная передача

В Беркли-ядрах фрагментация широковещательных дейтаграмм запрещена. Если размер IP-дейтаграммы, посылаемой на широковещательный адрес, превышает размер MTU исходящего интерфейса, возвращается ошибка

EMSGSIZE
[128, с. 233–234]. Эта стратегия впервые появилась в 4.2BSD. На самом деле нет никаких технических препятствий для фрагментирования широковещательных дейтаграмм, но широковещательная передача сама по себе связана со значительной нагрузкой на сеть, поэтому не стоит дополнительно увеличивать эту нагрузку, используя фрагментацию.

Можно наблюдать этот сценарий с нашей программой из листинга 20.1. Мы перенаправляем стандартный поток ввода для чтения из файла, содержащего 2000-байтовую строку, которая потребует фрагментации в Ethernet:

bsdi % <b>udpcli01 192.168.42.255 &lt; 2000line</b>

sendto error: Message too long

ПРИМЕЧАНИЕ

Это ограничение реализовано в AIX, FreeBSD и MacOS. Linux, Solaris и HP-UX фрагментируют дейтаграммы, отправленные на широковещательный адрес. Однако в целях переносимости приложение, которому нужно сделать широковещательный запрос, должно определять MTU для интерфейса, через который будет отправлено сообщение, при помощи параметра SIOCGIPMTU функции ioctl, после чего вычесть размер заголовков IP и транспортного протокола. Альтернативный подход: выбрать типичное значение MTU (например, 1500 для Ethernet) и использовать его в качестве константы.

20.5. Ситуация гонок

Ситуация гонок (race condition) обычно возникает, когда множество процессов получают доступ к общим для них данным, но корректность результата зависит от порядка выполнения процессов. Поскольку порядок выполнения процессов в типичных системах Unix зависит от множества факторов, которые могут меняться от запуска к запуску, иногда результат корректен, а иногда — нет. Наиболее сложным для отладки типом гонок является такой, когда результат получается некорректным только изредка. Более подробно о ситуациях гонок мы поговорим в главе 26, когда будем обсуждать взаимные исключения (mutex) и условные переменные (condition variables). При программировании потоков всегда возникают проблемы с ситуациями гонок, поскольку значительное количество данных является общим для всех потоков (например, все глобальные переменные).

Ситуации гонок другого типа часто возникают при работе с сигналами. Проблемы возникают, потому что сигнал, как правило, может быть доставлен в любой момент во время выполнения нашей программы. POSIX позволяет нам блокировать доставку сигнала, но при выполнении операций ввода-вывода это часто не дает эффекта.

Чтобы понять эту проблему, рассмотрим пример. Ситуация гонок возникает при выполнении программы из листинга 20.1. Потратьте несколько минут и посмотрите, сможете ли вы ее обнаружить. (Подсказка: в каком месте программы мы можем находиться, когда доставляется сигнал?) Вы можете также инициировать ситуацию гонок следующим образом: изменить аргумент функции

alarm
с 5 на 1 и добавить вызов
sleep(1)
сразу же после
printf
.

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