UNIX: разработка сетевых приложений - Уильям Стивенс
Шрифт:
Интервал:
Закладка:
//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 %sn", 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: %sn",
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 %sn", 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.
Порождение дочернего процесса43-47 Порождается дочерний процесс, и он вызывает функцию mydg_echo.
Заключительная часть функции main показана в листинге 22.15. В этом коде при помощи функции bind происходит связывание с универсальным адресом для обработки любого адреса получателя, отличного от адресов направленной и широковещательной передачи, которые уже связаны. На этот сокет будут приходить только дейтаграммы, предназначенные для ограниченного широковещательного адреса (255.255.255.255).
Листинг 22.15. Заключительная часть сервера UDP, связывающегося со всеми адресами
//advio/udpserv03.c
50 /* связываем универсальный адрес */
51 sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
52 Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
53 bzero(&wildaddr, sizeof(wildaddr));
54 wildaddr.sin_family = AF_INET;
55 wildaddr.sin_addr.s_addr = htonl(INADDR_ANY);
56 wildaddr.sin_port = htons(SERV_PORT);
57 Bind(sockfd, (SA*)&wildaddr, sizeof(wildaddr));
58 printf("bound %sn", Sock_ntop((SA*)&wildaddr, sizeof(wildaddr)));
59 if ((pid = Fork()) == 0) { /* дочерний процесс */
60 mydg_echo(sockfd, (SA*)&cliaddr, sizeof(cliaddr), (SA*)sa);
61 exit(0); /* не выполняется */
62 }
63 exit(0);
64 }
Создание сокета и связывание с универсальным адресом50-62 Создается сокет UDP, устанавливается параметр сокета SO_REUSEADDR и происходит связывание с универсальным IP-адресом. Порождается дочерний процесс, вызывающий функцию mydg_echo.
Завершение работы функции main63 Функция main завершается, и сервер продолжает выполнять работу, как и все порожденные дочерние процессы.
Функция mydg_echo, которая выполняется всеми дочерними процессами, показана в листинге 22.16.
Листинг 22.16. Функция mydg_echo
//advio/udpserv03.c
65 void
66 mydg_echo(int sockfd, SA *pcliaddr, socklen_t clilen, SA *myaddr)
67 {
68 int n;
69 char mesg[MAXLINE];
70 socklen_t len;
71 for (;;) {
72 len = clilen;
73 n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
74 printf("child %d, datagram from %s", getpid(),
75 Sock_ntop(pcliaddr, len));
76 printf(", to %sn", Sock_ntop(myaddr, clilen));
77 Sendto(sockfd, mesg, n, 0, pcliaddr, len);
78 }
79 }
Новый аргумент65-66 Четвертым аргументом этой функции является IP-адрес, связанный с сокетом. Этот сокет должен получать только дейтаграммы, предназначенные для данного IP-адреса. Если IP-адрес является универсальным, сокет должен получать только те дейтаграммы, которые не подходят ни для какого другого сокета, связанного с тем же портом.
Чтение дейтаграммы и отражение ответа71-78 Дейтаграмма читается с помощью функции recvfrom и отправляется клиенту обратно с помощью функции sendto. Эта функция также выводит IP-адрес клиента и IP-адрес, который был связан с сокетом.
Запустим эту программу на нашем узле solaris после установки псевдонима для интерфейса hme0 Ethernet. Адрес псевдонима: узел 200 в сети 10.0.0/24.
solaris % udpserv03
bound 127.0.0.1:9877 интерфейс закольцовки
bound 10.0.0.200:9877 направленный адрес интерфейса hme0:1
bound 10.0.0.255:9877 широковещательный адрес интерфейса hme0:1
bound 192.168.1.20:9877 направленный адрес интерфейса hme0
bound 192.168.1.255:9877 широковещательный адрес интерфейса hme0
bound 0.0.0.0.9877 универсальный адрес
При помощи утилиты netstat мы можем проверить, что все сокеты связаны с указанными IP-адресами и портом:
solaris % netstat -na | grep 9877
127.0.0.1.9877 Idle
10.0.0.200.9877 Idle
*.9877 Idle
192.129.100.100.9877 Idle
*.9877 Idle
*.9877 Idle
Следует отметить, что для простоты мы создаем по одному дочернему процессу на сокет, хотя возможны другие варианты. Например, чтобы ограничить число процессов, программа может управлять всеми дескрипторами сама, используя функцию select и не вызывая функцию fork. Проблема в данном случае будет заключаться в усложнении кода. Хотя использовать функцию select для всех дескрипторов несложно, нам придется осуществить некоторое сопоставление каждого дескриптора связанному с ним IP-адресу (вероятно, с помощью массива структур), чтобы иметь возможность вывести IP-адрес получателя после того, как на определенном сокете получена дейтаграмма. Часто бывает проще использовать отдельный процесс или поток для каждой операции или дескриптора вместо мультиплексирования множества различных операций или дескрипторов одним процессом.