ПРИМЕЧАНИЕ
Вспомните наше обсуждение в конце раздела 22.2. Если узел использует более распространенную модель системы с гибкой привязкой (см. раздел 8.8), IP-адрес получателя может отличаться от IP-адреса принимающего интерфейса. В этом случае мы можем определить только адрес получателя дейтаграммы, который не обязательно должен быть адресом, присвоенным принимающему интерфейсу. Чтобы определить принимающий интерфейс, требуется параметр сокета IP_RECVIF или IPV6_PKTINFO.
В листинге 22.13 показана первая часть примера применения этой технологии к эхо-серверу UDP, который связывается со всеми адресами направленной передачи, широковещательной передачи и, наконец, с универсальными адресами.
Листинг 22.13. Первая часть сервера UDP, который с помощью функции bind связывается со всеми адресами
//advio/udpserv03.c
1 #include "unpifi.h"
2 void mydg_echo(int, SA*, socklen_t, SA*);
3 int
4 main(int argc, char **argv)
5 {
6 int sockfd;
7 const int on = 1;
8 pid_t pid;
9 struct ifi_info *ifi, *ifihead;
10 struct sockaddr_in *sa, cliaddr, wildaddr;
11 for (ifihead = ifi = Get_ifi_info(AF_INET, 1);
12 ifi != NULL; ifi = ifi->ifi_next) {
13 /* связываем направленный адрес */
14 sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
15 Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
16 sa = (struct sockaddr_in*)ifi->ifi_addr;
17 sa->sin_family = AF_INET;
18 sa->sin_port = htons(SERV_PORT);
19 Bind(sockfd, (SA*)sa, sizeof(*sa));
20 printf("bound %s\n", Sock_ntop((SA*)sa, sizeof(*sa)));
21 if ((pid = Fork()) == 0) { /* дочерний процесс */
22 mydg_echo(sockfd, (SA*)&cliaddr, sizeof(cliaddr), (SA*)sa);
23 exit(0); /* не выполняется */
24 }
Вызов функции get_ifi_info для получения информации об интерфейсе
11-12
Функция
get_ifi_info
получает все адреса IPv4, включая дополнительные (псевдонимы), для всех интерфейсов. Затем программа перебирает все структуры
ifi_info
.
Создание сокета UDP и связывание адреса направленной передачи
13-20
Создается сокет UDP, и с ним связывается адрес направленной передачи. Мы также устанавливаем параметр сокета
SO_REUSEADDR
, поскольку мы связываем один и тот же порт (параметр
SERV_PORT
) для всех IP-адресов.
ПРИМЕЧАНИЕ
Не все реализации требуют, чтобы был установлен этот параметр сокета. Например, Беркли-реализации не требуют этого параметра и позволяют с помощью функции bind связать уже связанный порт, если новый связываемый IP-адрес не является универсальным адресом и отличается от всех IP-адресов, уже связанных с портом. Однако Solaris 2.5 для успешного связывания с одним и тем же портом второго адреса направленной передачи требует установки этого параметра.
Порождение дочернего процесса для данного адреса
21-24
Вызывается функция
fork
, порождающая дочерний процесс. В этом дочернем процессе вызывается функция
mydg_echo
, которая ждет прибытия любой дейтаграммы на сокет и отсылает ее обратно отправителю.
В листинге 22.14 показана следующая часть функции
main
, которая обрабатывает широковещательные адреса.
Листинг 22.14. Вторая часть сервера UDP, который с помощью функции bind связывается со всеми адресами
//advio/udpserv03.c
25 if (ifi->ifi_flags & IFF_BROADCAST) {
26 /* пытаемся связать широковещательный адрес */
27 sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
28 Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
29 sa = (struct sockaddr_in*)ifi->ifi_brdaddr;
30 sa->sin_family = AF_INET;
31 sa->sin_port = htons(SERV_PORT);
32 if (bind(sockfd, (SA*)sa, sizeof(*sa)) < 0) {
33 if (errno == EADDRINUSE) {
34 printf("EADDRINUSE: %s\n",
35 Sock_ntop((SA*)sa, sizeof(*sa)));
36 Close(sockfd);
37 continue;
38 } else
39 err_sys("bind error for %s",
40 Sock_ntop((SA*)sa, sizeof(*sa)));
41 }
42 printf("bound %s\n", Sock_ntop((SA*)sa, sizeof(*sa)));
43 if ((pid = Fork()) == 0) { /* дочерний процесс */
44 mydg_echo(sockfd, (SA*)&cliaddr, sizeof(cliaddr),
45 (SA*)sa);
46 exit(0); /* не выполняется */
47 }
48 }
49 }
Связывание с широковещательными адресами
25-42
Если интерфейс поддерживает широковещательную передачу, создается сокет UDP и с ним связывается широковещательный адрес. На этот раз мы позволим функции
bind
завершиться с ошибкой
EADDRINUSE
, поскольку если у интерфейса имеется несколько дополнительных адресов (псевдонимов) в одной подсети, то каждый из различных адресов направленной передачи будет иметь один и тот же широковещательный адрес. Подобный пример приведен после листинга 17.3. В этом сценарии мы предполагаем, что успешно выполнится только первая функция
bind
.