UNIX: разработка сетевых приложений - Уильям Стивенс
Шрифт:
Интервал:
Закладка:
Листинг 23.7. Функция, связывающая сокет с набором адресов
1 #include "unp.h"
2 int
3 sctp_bind_arg_list(int sock_fd, char **argv, int argc)
4 {
5 struct addrinfo *addr;
6 char *bindbuf, *p, portbuf[10];
7 int addrcnt=0;
8 int i;
9 bindbuf = (char*)Calloc(argc, sizeof(struct sockaddr_storage));
10 p = bindbuf;
11 sprintf(portbuf, "%d", SERV_PORT);
12 for (i=0; i<argc; i++ ) {
13 addr = Host_serv(argv[i], portbuf, AF_UNSPEC, SOCK_SEQPACKET);
14 memcpy(p, addr->ai_addr, addr->ai_addrlen);
15 freeaddrinfo(addr);
16 addrcnt++;
17 p += addr->ai_addrlen;
18 }
19 Sctp_bindx(sock_fd, (SA*)bindbuf, addrent, SCTP_BINDX_ADD_ADDR);
20 free(bindbuf);
21 return(0);
22 }
Выделение памяти под аргументы bind9-10 Наша новая функция начинает работу с выделения памяти под аргументы функции sctp_bindx. Обратите внимание, что функция sctp_bindx может принимать в качестве аргументов адреса IPv4 и IPv6 в произвольных комбинациях. Для каждого адреса мы выделяем место под структуру sockaddr_storage несмотря на то, что соответствующий аргумент sctp_bindx представляет собой упакованный список адресов (см. рис. 9.3). В результате мы расходуем зря некоторый объем памяти, но зато функция работает быстрее, потому что ей не приходится вычислять точный объем памяти и лишний раз обрабатывать список аргументов.
Обработка аргументов11-18 Мы подготавливаем portbuf к хранению номера порта в ASCII-представлении, имея в виду вызов нашей обертки для getaddrinfo, которая называется host_serv. Каждый адрес с номером порта мы передаем host_serv, указывая константы AF_UNSPEC (протоколы IPv4 и IPv6) и SOCK_SEQPACKET (протокол SCTP). Мы копируем первую возвращаемую структуру sockaddr, игнорируя все остальные. Поскольку аргументами этой функции должны быть адреса в строковом представлении, а не имена, с каждым из которых может быть связано несколько адресов, это не вызывает проблем. Мы освобождаем буфер, увеличиваем количество адресов на единицу и перемещаем указатель на следующий элемент в упакованном массиве структур sockaddr.
Вызов связывающей функции19 Указатель устанавливается на начало буфера адресов, после чего вызывается функция sctp_bindx, в качестве аргументов которой используется раскодированный ранее набор адресов.
Успешное завершение20-21 Если мы добрались до этого места, можно считать, что выполнение прошло успешно, поэтому мы освобождаем память и возвращаем управление вызвавшему процессу.
В листинге 23.8 представлен модифицированный эхо-сервер, связывающий сокет с набором адресов, передаваемых в командной строке. Мы слегка изменили код сервера, чтобы он отправлял эхо-сообщения по тем потокам, по которым были приняты исходные сообщения.
Листинг 23.8. Сервер, работающий с произвольным набором адресов
if (argc < 2)
err_quit("Error, use %s [list of addresses to bind]n", argv[0]);
sock_fd = Socket(AF_INET6, SOCK_SEQPACKET, IPPROTO_SCTP);
if (sctp_bind_arg_list(sock_fd, argv + 1, argc — 1))
err_sys("Can't bind the address set");
bzero(&evnts, sizeof(evnts));
evnts sctp_data_io_event = 1;
Работа с IPv614 Это тот же сервер, с которым мы работали во всех предыдущих разделах этой главы, но с незначительным изменением. Сервер создает сокет AF_INET6, что позволяет ему работать с протоколом IP обеих версий.
Вызов sctp_bind_arg_list15-16 Сервер вызывает новую функцию sctp_bind_arg_list и передает ей список аргументов для обработки.
23.7. Получение адресов
Поскольку протокол SCTP ориентирован на многоинтерфейсные узлы, для определения адресов локального и удаленного узла не могут использоваться те же механизмы, что и в TCP. В этом разделе мы изменим код клиента, добавив в него подписку на уведомление о событии COMMUNICATION UP. В этом уведомлении клиент будет получать сведения об адресах, между которыми установлена ассоциация. В листингах 23.9 и 23.10 представлены изменения в коде клиента. Листинги 23.11 и 23.12 содержат добавления к коду клиента.
Листинг 23.9. Клиент включает уведомления
16 bzero(&evnts, sizeof(evnts));
17 evnts.sctp_data_io_event = 1;
18 evnts.sctp_association_event = 1;
19 Setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &evnts, sizeof(evnts));
20 sctpstr_cli(stdin, sock_fd, (SA*)&servaddr, sizeof(servaddr));
Включение событий и вызов функции отправки сообщения16-20 Функция main клиента претерпевает не слишком значительные изменения. Клиент явным образом подписывается на уведомления об изменении состояния ассоциации.
Посмотрим, что нам придется изменить в функции sctpstr_cli, чтобы она смогла вызывать нашу новую функцию обработки уведомлений.
Листинг 23.10. Функция sctp_strcli, способная работать с уведомлениями
21 do {
22 len = sizeof(peeraddr);
23 rd_sz = Sctp_recvmsg(sock_fd, recvline, sizeof(recvline),
24 (SA*)&peeraddr, &len, &sri, &msg_flags);
25 if (msg_flags & MSG_NOTIFICATION)
26 check_notification(sock_fd, recvline, rd_sz);
27 } while (msg_flags & MSG_NOTIFICATION);
28 printf("From str:%d seq:%d (assoc.0x%x) ",
29 sri.sinfo_stream, sri.sinfo_ssn, (u_int)sri.sinfo_assoc_id);
30 printf("%.*s", rd_sz.recvline);
Цикл ожидания сообщения21-24 Клиент устанавливает переменную, в которой хранится длина адреса, и вызывает функцию sctp_recvmsg для получения эхо-ответа сервера на свое сообщение.
Проверка уведомлений25-26 Клиент проверяет, не является ли полученное сообщение уведомлением. В последнем случае он вызывает функцию обработки уведомлений, представленную в листинге 23.11.
Переход на начало цикла27 Если сообщение действительно было уведомлением, происходит переход на начало цикла ожидания сообщений.
Отображение сообщения28-30 Клиент отображает сообщение и переходит к ожиданию пользовательского ввода.
Теперь мы можем изучить новую функцию sctp_check_notification, которая будет отображать адреса обоих конечных точек при получении уведомления об изменении состояния ассоциации.
Листинг 23.11. Обработка уведомлений
//sctp/sctp_check_notify.c
1 #include "unp.h"
2 void
3 check_notification(int sock_fd, char *recvline, int rd_len)
4 {
5 union sctp_notification *snp;
6 struct sctp_assoc_change *sac;
7 struct sockaddr_storage *sal, *sar;
8 int num_rem, num_loc;
9 snp = (union sctp_notification*)recvline;
10 if (snp->sn_header.sn_type == SCTP_ASSOC_CHANGE) {
11 sac = &snp->sn_assoc_change;
12 if ((sac->sac_state == SCTP_COMM_UP) ||
13 (sac->sac_state == SCTP_RESTART)) {
14 num_rem = sctp_getpaddrs(sock_fd, sac->sac_assoc_id, &sar);
15 printf("There are %d remote addresses and they are:n", num_rem);
16 sctp_print_addresses(sar, num_rem);
17 sctp_freepaddrs(sar);
18 num_loc = sctp_getladdrs(sock_fd.sac->sac_assoc_id, &sal);
19 printf("There are %d local addresses and they are:n", num_loc);
20 sctp_print_addresses(sal, num_loc);
21 sctp_freeladdrs(sal);
22 }
23 }
24 }
Проверка типа уведомления9-13 Функция преобразует буфер приема к типу универсального указателя на уведомления, чтобы определить тип полученного уведомления. Из всех уведомлений нас интересуют только уведомления об изменении ассоциации, а из них — уведомления о создании или перезапуске ассоциации (SCTP_COMM_UP и SCTP_RESTART). Все прочие уведомления нас не интересуют.
Получение и вывод адресов собеседника14-17 Функция sctp_getpaddrs возвращает нам список удаленных адресов, которые мы выводим при помощи функции sctp_print_addresses, представленной в листинге 23.12. После работы с ней мы освобождаем ресурсы, выделенные sctp_getpaddrs, вызывая функцию sctp_freepaddrs.
Получение и вывод локальных адресов18-21 Функция sctp_getladdrs возвращает нам список локальных адресов, которые мы выводим на экран вместе с их общим количеством. После завершения работы с адресами мы освобождаем память вызовом sctp_freeladdrs.
Последняя из новых функций называется sctp_print_addresses. Она выводит на экран адреса из списка, возвращаемого функциями sctp_getpaddrs и sctp_getladdrs. Текст функции представлен в листинге 23.12.
Листинг 23.12. Вывод списка адресов
//sctp/sctp_print_addrs.c
1 #include "unp.h"
2 void
3 sctp_print_addresses(struct sockaddr_storage *addrs, int num)
4 {
5 struct sockaddr_storage *ss;
6 int i, salen;
7 ss = addrs;