Текст книги "UNIX: разработка сетевых приложений"
Автор книги: Уильям Ричард Стивенс
Соавторы: Эндрю М. Рудофф,Билл Феннер
Жанр:
ОС и Сети
сообщить о нарушении
Текущая страница: 19 (всего у книги 88 страниц) [доступный отрывок для чтения: 32 страниц]
Теперь мы изменим наш эхо-сервер TCP из раздела 6.8, используя вместо функции select
функцию poll
. В предыдущей версии сервера, работая с функцией select
, мы должны были выделять массив client
вместе с набором дескрипторов rset
(см. рис. 6.12). С помощью функции poll
мы разместим в памяти массив структур pollfd
. В нем же мы будем хранить и информацию о клиенте, не создавая для нее другой массив. Элемент fd
этого массива мы обрабатываем тем же способом, которым обрабатывали массив client
(см. рис. 6.12): значение -1 говорит о том, что элемент не используется, а любое другое значение является номером дескриптора. Вспомните из предыдущего раздела, что любой элемент в массиве структур pollfd
, передаваемый функции poll
с отрицательным значением элемента fd
, просто игнорируется.
В листинге 6.5 показана первая часть кода нашего сервера.
Листинг 6.5. Первая часть сервера TCP, использующего функцию poll
//tcpcliserv/tcpservpoll01.с
1 #include "unp.h"
2 #include <1imits.h> /* для OPEN_MAX */
3 int
4 main(int argc, char **argv)
5 {
6 int i, maxi, listenfd, connfd, sockfd;
7 int nready;
8 ssize_t n;
9 char buf[MAXLINE];
10 socklen_t clilen;
11 struct pollfd client[OPEN_MAX];
12 struct sockaddr_in cliaddr, servaddr;
13 listenfd = Socket(AF_INET, SOCK_STREAM, 0);
14 bzero(&servaddr, sizeof(servaddr));
15 servaddr.sin_family = AF_INET;
16 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
17 servaddr.sin_port = htons(SERV_PORT);
18 Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));
19 Listen(listenfd, LISTENQ);
20 client[0].fd = listenfd;
21 client[0].events = POLLRDNORM;
22 for (i = 1; i < OPEN_MAX; i++)
23 client[i].fd = -1; /* -1 означает, что элемент свободен */
24 maxi = 0; /* максимальный индекс массива client[] */
Размещение массива структур pollfd в памяти
11
Мы объявляем массив структур pollfd
размером OPEN_MAX
. Не существует простого способа определить максимальное число дескрипторов, которые могут быть открыты процессом. Мы снова столкнемся с этой проблемой в листинге 13.1. Один из способов ее решения – вызвать функцию POSIX sysconf
с аргументом _SC_OPEN_MAX
[110, с. 42-44], а затем динамически выделять в памяти место для массива соответствующего размера. Однако функция sysconf
может возвратить некое «неопределенное» значение, и в этом случае нам придется задавать ограничение самим. Здесь мы используем только константу OPEN_MAX
стандарта POSIX.
Инициализация
20-24
Мы используем первый элемент в массиве client
для прослушиваемого сокета и присваиваем дескрипторам для оставшихся элементов -1. Мы также задаем в качестве аргумента функции poll
событие POLLRDNORM
, чтобы получить уведомление от этой функции в том случае, когда новое соединение будет готово к приему. Переменная maxi
содержит максимальный индекс массива client
, используемый в настоящий момент.
Вторая часть нашей функции приведена в листинге 6.6.
Листинг 6.6. Вторая часть сервера TCP, использующего функцию poll
//tcpcliserv/tcpservpoll01.c
25 for (;;) {
26 nready = Poll(client, maxi + 1, INFTIM);
27 if (client[0].revents & POLLRDNORM) { /* новое соединение
с клиентом */
28 clilen = sizeof(cliaddr);
29 connfd = Accept(listenfd. (SA*)&cliaddr, &clilen);
30 for (i = 1; i < OPEN_MAX; i++)
31 if (client[1].fd < 0) {
32 client[i].fd = connfd; /* сохраняем дескриптор */
33 break;
34 }
35 if (i == OPEN_MAX)
36 err_quit("too many clients");
37 client[i].events = POLLRDNORM;
38 if (i > maxi)
39 maxi = i; /* максимальный индекс в массиве client[] */
40 if (–nready <= 0)
41 continue; /* больше нет дескрипторов, готовых для чтения */
42 }
43 for (i = 1; i <= maxi; i++) { /* проверяем все клиенты на наличие
данных */
44 if ((sockfd = client[i].fd) < 0)
45 continue;
46 if (client[i].revents & (POLLRDNORM | POLLERR)) {
47 if ((n = Read(sockfd, buf, MAXLINE)) < 0) {
48 if (errno == ECONNRESET) {
49 /* соединение переустановлено клиентом */
50 Close(sockfd);
51 client[i].fd = -1;
52 } else
53 err_sys("readline error");
54 } else if (n == 0) {
55 /* соединение закрыто клиентом */
56 Close(sockfd);
57 client[i].fd = -1;
58 } else
59 Writen(sockfd, line, n);
60 if (–nready <= 0)
61 break; /* больше нет дескрипторов, готовых для чтения */
62 }
63 }
64 }
65 }
Вызов функции poll, проверка нового соединения
26-42
Мы вызываем функцию poll
для ожидания нового соединения либо данных на существующем соединении. Когда новое соединение принято, мы находим первый свободный элемент в массиве client
– это первый элемент с отрицательным дескриптором. Обратите внимание, что мы начинаем поиск с индекса 1, поскольку элемент client[0]
используется для прослушиваемого сокета. Когда свободный элемент найден, мы сохраняем дескриптор и устанавливаем событие POLLRDNORM
.
Проверка данных на существующем соединении
43-63
Два события, которые нас интересуют, – это POLLRDNORM
и POLLERR
. Второй флаг в элементе event
мы не устанавливали, поскольку этот флаг возвращается всегда, если соответствующее условие выполнено. Причина, по которой мы проверяем событие POLLERR
, в том, что некоторые реализации возвращают это событие, когда приходит сегмент RST, другие же в такой ситуации возвращают событие POLLRDNORM
. В любом случае мы вызываем функцию read
, и если произошла ошибка, эта функция возвратит ее. Когда существующее соединение завершается клиентом, мы просто присваиваем элементу fd
значение -1.
В Unix существует пять различных моделей ввода-вывода:
■ блокируемый ввод-вывод;
■ неблокируемый ввод-вывод;
■ мультиплексирование ввода-вывода;
■ управляемый сигналом ввод-вывод;
■ асинхронный ввод-вывод.
По умолчанию используется блокируемый ввод-вывод, и этот вариант встречается наиболее часто. Неблокируемый ввод-вывод и управляемый сигналом ввод-вывод мы рассмотрим в последующих главах. В этой главе мы рассмотрели мультиплексирование ввода-вывода. Асинхронный ввод-вывод определяется в стандарте POSIX, но поддерживающих его реализаций не так много.
Наиболее часто используемой функцией для мультиплексирования ввода– вывода является функция select
. Мы сообщаем этой функции, какие дескрипторы нас интересуют (для чтения, записи или условия ошибки), а также передаем ей максимальное время ожидания и максимальное число дескрипторов (увеличенное на единицу). Большинство вызовов функции select
определяют количество дескрипторов, готовых для чтения, и, как мы отметили, единственное условие исключения при работе с сокетами – это прибытие внеполосных данных (см. главу 21). Поскольку функция select
позволяет ограничить время блокирования функции, мы используем это свойство в листинге 14.3 для ограничения по времени операции ввода.
Используя эхо-клиент в пакетном режиме с помощью функции select
, мы выяснили, что даже если обнаружен признак конца файла, данные все еще могут находиться в канале на пути к серверу или от сервера. Обработка этого сценария требует применения функции shutdown
, которая позволяет воспользоваться таким свойством TCP, как возможность половинного закрытия соединения (half-close feature).
POSIX определяет функцию pselect
(повышающую точность таймера с микросекунд до наносекунд) которой передается новый аргумент – указатель на набор сигналов. Это позволяет избежать ситуации гонок (race condition) при перехвате сигналов, о которой мы поговорим более подробно в разделе 20.5.
Функция poll
из System V предоставляет функциональность, аналогичную функции select
. Кроме того, она обеспечивает дополнительную информацию при работе с потоковыми устройствами. POSIX требует наличия и функции select
, и функции poll
, но первая распространена шире.
1. Мы говорили, что набор дескрипторов можно присвоить другому набору дескрипторов, используя оператор присваивания языка С. Как это сделать, если набор дескрипторов является массивом целых чисел? ( Подсказка: посмотрите на свой системный заголовочный файл
или
.)
2. Описывая в разделе 6.3 условия, при которых функция select
сообщает, что дескриптор готов для записи, мы указали, что сокет должен быть неблокируемым, для того чтобы операция записи возвратила положительное значение. Почему?
3. Что произойдет с программой из листинга 6.1, если мы поставим слово else
перед if
в строке 19?
4. В листинге 6.3 добавьте необходимый код, чтобы позволить серверу использовать максимальное число дескрипторов, допустимое ядром ( Подсказка: изучите функцию setrlimit
.)
5. Посмотрите, что происходит, если в качестве второго аргумента функции shutdown
передается SHUT_RD
. Возьмите за основу код клиента TCP, представленный в листинге 5.3, и выполните следующие изменения: вместо номера порта SERV_PORT
задайте порт 19 (служба chargen
, см. табл. 2.1), а также замените вызов функции str_cli
вызовом функции pause
. Запустите программу, задав IP-адрес локального узла, на котором выполняется сервер chargen
. Просмотрите пакеты с помощью такой программы, как, например, tcpdump
(см. раздел В.5). Что происходит?
6. Почему приложение должно вызывать функцию shutdown
с аргументом SHUT_RDWR
, вместо того чтобы просто вызвать функцию close
?
7. Что происходит в листинге 6.4, когда клиент отправляет RST для завершения соединения?
8. Перепишите код, показанный в листинге 6.5, чтобы вызывать функцию sysconf
для определения максимального числа дескрипторов и размещения соответствующего массива client
в памяти.
Глава 7
Параметры сокетов
7.1. ВведениеСуществуют различные способы получения и установки параметров сокетов:
■ функции getsockopt
и setsockopt
;
■ функция fcntl
;
■ функция ioctl
.
Эту главу мы начнем с описания функций getsockopt
и setsockopt
. Далее мы приведем пример, в котором выводятся заданные по умолчанию значения параметров, а затем дадим подробное описание всех параметров сокетов. Мы разделили описание параметров на следующие категории: общие, IPv4, IPv6, TCP и SCTP. При первом прочтении главы можно пропустить подробное описание параметров и при необходимости прочесть отдельные разделы, на которые даны ссылки. Отдельные параметры подробно описываются в дальнейших главах, например параметры многоадресной передачи IPv4 и IPv6 мы обсуждаем в разделе 19.5.
Мы также рассмотрим функцию fcntl
, поскольку она реализует предусмотренные стандартом POSIX возможности отключить для сокета блокировку ввода-вывода, включить управление сигналами, а также установить владельца сокета. Функцию ioctl
мы опишем в главе 17.
Эти две функции применяются только к сокетам.
#include
int getsockopt(int sockfd, int level, int optname, void * optval, socklen_t * optlen);
int setsockopt(int sockfd, int level, int optname, const void * optval, socklen_t optlen);
Обе функции возвращают 0 в случае успешного завершения, -1 в случае ошибки
Переменная sockfd
должна ссылаться на открытый дескриптор сокета. Переменная level
определяет, каким кодом должен интерпретироваться параметр: общими программами обработки сокетов или зависящими от протокола программами (например, IPv4, IPv6, TCP или SCTP).
optval
– это указатель на переменную, из которой извлекается новое значение параметра с помощью функции setsockopt
или в которой сохраняется текущее значение параметра с помощью функции getsockopt
. Размер этой переменной задается последним аргументом. Для функции setsockopt
тип этого аргумента – значение, а для функции getsockopt
– « значение-результат».
В табл. 7.1 и 7.2 сведены параметры, которые могут запрашиваться функцией getsockopt
или устанавливаться функцией setsockopt
. В колонке «Тип данных» приводится тип данных того, на что указывает указатель optval
для каждого параметра. Две фигурные скобки мы используем, чтобы обозначить структуру, например linger{}
обозначает struct linger
.
Таблица 7.1. Параметры сокетов для функций getsockopt и setsockopt
SOL_SOCKET | SO_BROADCAST | • | • | Позволяет посылать широковещательные дейтаграммы | • | int |
SO_DEBUG | • | • | Разрешает отладку | • | int | |
SO_DONTROUTE | • | • | Обходит таблицу маршрутизации | • | int | |
SO_ERROR | • | Получает ошибку, ожидающую обработки, и возвращает значение параметра в исходное состояние | int | |||
SO_KEEPALIVE | • | • | Периодически проверяет, находится ли соединение в рабочем состоянии | • | int | |
SO_LINGER | • | • | Задерживает закрытие сокета, если имеются данные для отправки | linger{} | ||
SO_OOBINLINE | • | • | Оставляет полученные внеполосные данные вместе с обычными данными (inline) | • | int | |
SO_RCVBUF | • | • | Размер приемного буфера | int | ||
SO_SNDBUF | • | • | Размер буфера отправки | int | ||
SO_RCVLOWAT | • | • | Минимальное количество данных для приемного буфера сокета | int | ||
SO_SNDLOWAT | • | • | Минимальное количество данных для буфера отправки сокета | int | ||
SO_RCVTIMEO | • | • | Тайм-аут при получении | timeval{} | ||
SO_SNDTIMEO | • | • | Тайм-аут при отправке | timeval{} | ||
SO_REUSEADDR | • | • | Допускает повторное использование локального адреса | • | int | |
SO_REUSEPORT | • | • | Допускает повторное использование локального адреса | • | int | |
SO_TYPE | • | Возвращает тип сокета | int | |||
SO_USELOOPBACK | • | • | Маршрутизирующий сокет получает копию того, что он отправляет | • | int | |
IPPROTO_IP | IP_HDRINCL | • | • | Включается IP– заголовок | • | int |
IP_OPTIONS | • | • | В заголовке IPv4 устанавливаются параметры IP | см. текст | ||
IP_RECVDSTADDR | • | • | Возвращает IP-адрес получателя | • | int | |
IP_RECVIF | • | • | Возвращает индекс интерфейса, на котором принимается дейтаграмма UDP | • | int | |
IP_TOS | • | • | Тип сервиса и приоритет | int | ||
IP_TTL | • | • | Время жизни | int | ||
IP_MULTICAST_IF | • | • | Задает интерфейс для исходящих дейтаграмм | in_addr{} | ||
IP_MULTICAST_TTL | • | • | Задает TTL для исходящих дейтаграмм | u_char | ||
IP_MULTICAST_LOOP | • | • | Разрешает или отменяет отправку копии дейтаграммы на тот узел, откуда она была послана (loopback) | u_char | ||
IP_ADD_MEMBERSHIP | • | Включение в группу многоадресной передачи | ip_mreq{} | |||
IP_DROP_MEMBERSHIP | • | Отключение от группы многоадресной передачи | ip_mreq{} | |||
IP_{BLOCK, UNBLOCK}_SOURCE | • | Блокирование и разблокирование источника многоадресной передачи | ip_mreq_source{} | |||
IP_{ADD, DROP}_SOURCE_MEMBERSHIP | • | Присоединение или отключение от многоадресной передачи от источника (source-specific) | ip_mreq_source{} | |||
IPPROTO_ICMPV6 | ICMP6_FILTER | • | • | Указывает тип сообщения ICMPv6, которое передается процессу | icmp6_filter{} | |
IPPROTO_IPV6 | IPV6_ADDRFORM | • | • | Меняет формат адреса сокета | int | |
IPV6_CHECKSUM | • | • | Отступ поля контрольной суммы для символьных (неструктурированных) сокетов | int | ||
IPV6_DONTFRAG | • | • | Не фрагментировать, а сбрасывать большие пакеты | • | int | |
IPV6_NEXTHOP | • | • | Задает следующий транзитный адрес | • | sockaddr{} | |
IPV6_PATHMTU | • | Получение текущей маршрутной МТУ | ip6_mtuinfo{} | |||
IPV6_RECVDSTOPTS | • | • | Получение параметров адресата | • | int | |
IPV6_RECVHOPLIMIT | • | • | Получение ограничения на количество транзитных узлов при направленной передаче | • | int | |
IPV6_RECVHOPOPTS | • | • | Получение параметров прыжков | • | int | |
IPV6_RECVPATHMTU | • | • | Получение маршрутной MTU | • | int | |
IPV6_RECVPKTINFO | • | • | Получение информации о пакетах | • | int | |
IPV6_RECVRTHDR | • | • | Получение маршрута от источника | • | int | |
IPV6_RECVTCLASS | • | • | Получение класса трафика | • | int | |
IPV6_UNICAST_HOPS | • | • | Предел количества транзитных узлов, задаваемый по умолчанию | int | ||
IPV6_USE_MIN_MTU | • | • | Использовать минимальную MTU | • | int | |
IPV6_V60NLY | • | • | Отключить совместимость с IPv4 | • | int | |
IPV6_XXX | • | • | Вспомогательные данные | см. текст | ||
IPV6_MULTICAST_IF | • | • | Задает интерфейс для исходящих дейтаграмм | u_int | ||
IPV6_MULTICAST_HOPS | • | • | Задает предельное количество транзитных узлов для исходящих широковещательных сообщений | int | ||
IPV6_MULTICAST_LOOP | • | • | Разрешает или отменяет отправку копии дейтаграммы на тот узел, откуда она была послана (loopback) | • | u_int | |
IPV6_LEAVE_GROUP | • | Выход из группы многоадресной передачи | ipv6_mreq{} | |||
IPPROTO_IP или IPPROTO_IPV6 | MCAST_JOIN_GROUP | • | Присоединение к группе многоадресной передачи | group_req{} | ||
MCAST_LEAVE_GROUP | • | Выход из группы многоадресной передачи | group_source_req{} | |||
MCAST_BLOCK_SOURCE | • | Блокирование источника многоадресной передачи | group_source_req{} | |||
MCAST_UNBLOCK_SOURCE | • | Разблокирование источника многоадресной передачи | group_source_req{} | |||
MCAST_JOIN_SOURCE_GROUP | • | Присоединение к группе многоадресной передачи от источника | group_source_req{} | |||
MCAST_LEAVE_SOURCE_GROUP | • | Выход из группы многоадресной передачи от источника | group_source_req{} |
Таблица 7.2. Параметры сокетов транспортного уровня
IPPROTO_TCP | TCP_MAXSEG | • | • | Максимальный размер сегмента TCP | int | |
TCP_NODELAY | • | • | Отключает алгоритм Нагла | • | int | |
IPPROTO_SCTP | SCTP_ADAPTION_LAYER | • | • | Указание на уровень адаптации | sctp_setadaption | |
SCTP_ASSOCINFO | + | • | Получение и задание сведений об ассоциации | sctp_assocparamms{} | ||
SCTP_AUTOCLOSE | • | • | Автоматическое закрытие | int | ||
SCTP_DEFAULT_SEND_PARAM | • | • | Параметры отправки но умолчанию | sctp_sndrcvinfo{} | ||
SCTP_DISABLE_FRAGMENTS | • | • | Фрагментация SCTP | • | int | |
SCTP_EVENTS | • | • | Уведомление об интересующих событиях | sctp_event_subscribe{} | ||
SCTP_GET_PEER_ADDR_INFO | + | Получение состояния адреса собеседника | sctp_paddrinfo{} | |||
SCTP_I_WANT_MAPPED_V4_ADDR | • | • | Отображение адресов IPv4 | • | int | |
SCTP_INITMSG | • | • | Параметры пакета INIT по умолчанию | sctp_initmsg{} | ||
SCTP_MAXBURST | • | • | Максимальный размер набора пакетов | int | ||
SCTP_MAXSEG | • | • | Максимальный размер фрагментации | int | ||
SCTP_NODELAY | • | • | Отключение алгоритма Нагла | • | int | |
SCTP_PEER_ADDR_PARAMS | + | • | Параметры адреса собеседника | sctp_paddrparams{) | ||
SCTP_PRIMARY_ADDR | + | • | Основной адрес назначения | sctp_setprim{} | ||
SCTP_RTOINFO | + | • | Информация RTO | sctp_rtoinfo{} | ||
SCTP_SET_PEER_PRIMARY_ADDR | • | Основной адрес назначения собеседника | sctp_setpeerprim{} | |||
SCTP_STATUS | + | Получение сведений о статусе ассоциации | sctp_status{} |
Существует два основных типа параметров: двоичные параметры, включающие или отключающие определенное свойство (флаги), и параметры, получающие и возвращающие значения параметров, которые мы можем либо задавать, либо проверять. В колонке «Флаг» указывается, относится ли параметр к флагам. Для флагов при вызове функции getsockopt
аргумент *optval
является целым числом. Возвращаемое значение *optval
нулевое, если параметр отключен, и ненулевое, если параметр включен. Аналогично, функция setsockopt
требует ненулевого значения *optval
для включения параметра, и нулевого значения – для его выключения. Если в колонке «Флаг» не содержится символа «•», то параметр используется для передачи значения заданного типа между пользовательским процессом и системой.
В последующих разделах этой главы приводятся дополнительные подробности о параметрах сокетов.
7.3. Проверка наличия параметра и получение значения по умолчаниюНапишем программу, которая проверяет, поддерживается ли большинство параметров, представленных в табл. 7.1 и 7.2, и если да, то выводит их значения, заданные по умолчанию. В листинге 7.1 [1]1
Все исходные коды программ, опубликованные в этой книге, вы можете найти по адресу http://www.piter.com.
[Закрыть]содержатся объявления нашей программы.
Листинг 7.1. Объявления для нашей программы, проверяющей параметры сокетов
//sockopt/checkopts.с
1 #include "unp.h"
2 #include
3 union val {
4 int i_val;
5 long l_val;
6 struct linger linger_val;
7 struct timeval timeval_val;
8 } val;
9 static char *sock_str_flag(union val*, int);
10 static char *sock_str_int(union val*, int);
11 static char *sock_str_linger(union val*, int);
12 static char *sock_str_timeval(union val*, int);
13 struct sock_opts {
14 const char *opt_str;
15 int opt_level;
16 int opt_name;
17 char *(*opt_val_str)(union val*, int);
18 } sock_opts[] = {
19 { "SO_BROADCAST", SOL_SOCKET, SO_BROADCAST, sock_str_flag },
20 { "SO_DEBUG", SOL_SOCKET, SO_DEBUG, sock_str_flag },
21 { "SO_DONTROUTE", SOL_SOCKET, SO_DONTROUTE, sock_str_flag },
22 { "SO_ERROR", SOL_SOCKET, SO_ERROR, sock_str_int },
23 { "SO_KEEPALIVE", SOL_SOCKET, SO_KEEPALIVE, sock_str_flag },
24 { "SO_LINGER", SOL_SOCKET, SO_LINGER, sock_str_linger },
25 { "SO_OOBINLINE", SOL_SOCKET, SO_OOBINLINE, sock_str_flag },
26 { "SO_RCVBUF", SOL_SOCKET, SO_RCVBUF, sock_str_int },
27 { "SO_SNDBUF", SOL_SOCKET, SO_SNDBUF, sock_str_int },
28 { "SO_RCVLOWAT", SOL_SOCKET, SO_RCVLOWAT, sock_str_int },
29 { "SO_SNDLOWAT", SOL_SOCKET, SO_SNDLOWAT, sock_str_int },
30 { "SO_RCVTIMEO", SOL_SOCKET, SO_RCVTIMEO, sock_str_timeval },
31 { "SO_SNDTIMEO", SOL_SOCKET, SO_SNDTIMEO, sock_str_timeval },
32 { "SO_REUSEADDR", SOL_SOCKET, SO_REUSEADDR, sock_str_flag },
33 #ifdef SO_REUSEPORT
34 { "SO_REUSEPORT", SOL_SOCKET, SO_REUSEPORT, sock_str_flag },
35 #else
36 { "SO_REUSEPORT", 0, 0, NULL },
37 #endif
38 { "SO_TYPE", SOL_SOCKET, SO_TYPE, sock_str_int },
39 { "SO_USELOOPBACK", SOL_SOCKET, SO_USELOOPBACK, sock_str_flag },
40 { "IP_TOS", IPPROTO_IP, IP_TOS, sock_str_int },
41 { "IP_TTL", IPPROTO_IP, IP_TTL, sock_str_int },
42 { "IPV6_DONTFRAG", IPPROTO_IPV6, IPV6_DONTFRAG, sock_str_flag },
43 { "IPV6_UNICAST_HOPS", IPPROTO_IPV6, IPV6_UNICAST_HOPS, sock_str_int },
44 { "IPV6_V6ONLY", IPPROTO_IPV6, IPV6_V6ONLY, sock_str_flag },
45 { "TCP_MAXSEG", IPPROTO_TCP, TCP_MAXSEG, sock_str_int },
46 { "TCP_NODELAY", IPPROTO_TCP, TCP_NODELAY, sock_str_flag },
47 { "SCTP_AUTOCLOSE", IPPROTO_SCTP, SCTP_AUTOCLOSE, sock_str_int },
48 { "SCTP_MAXBURST", IPPROTO_SCTP, SCTP_MAXBURST, sock_str_int },
49 { "SCTP_MAXSEG", IPPROTO_SCTP, SCTP_MAXSEG, sock_str_int },
50 { "SCTP_NODELAY", IPPROTO_SCTP, SCTP_NODELAY, sock_str_flag },
51 { NULL, 0, 0, NULL }
52 };
Объявление объединения возможных значений
3-9
Наше объединение val
содержит по одному элементу для каждого возможного возвращаемого значения из функции getsockopt
.
Задание прототипов функций
10-13
Мы определяем прототипы для четырех функций, которые вызываются для вывода значения данного параметра сокета.
Задание структуры и инициализация массива
14-46
Наша структура sock_opts
содержит всю информацию, которая необходима, чтобы вызвать функцию getsockopt
для каждого из параметров сокета и вывести его текущее значение. Последний элемент, opt_val_str
, является указателем на одну из четырех функций, которые выводят значение параметра. Мы размещаем в памяти и инициализируем массив этих структур, каждый элемент которого соответствует одному параметру сокета.
ПРИМЕЧАНИЕ
Не все реализации поддерживают полный набор параметров сокетов. Чтобы определить, поддерживается ли данный параметр, следует использовать #ifdef или #if defined, как показано для параметра SO_REUSEPORT. Для полноты картины требуется обработать подобным образом все параметры, но в книге мы пренебрегаем этим, потому что #ifdef только удлиняет показанный код и не влияет на суть дела.
В листинге 7.2 показана наша функция main
.
Листинг 7.2. Функция main для проверки параметров сокетов
//sockopt/checkopts.c
53 int
54 main(int argc, char **argv)
55 {
56 int fd;
57 socklen_t len;
58 struct sock_opts *ptr;
59 for (ptr = sock_opts; ptr->opt_str != NULL; ptr++) {
60 printf("%s: ptr->opt_str);
61 if (ptr->opt_val_str == NULL)
62 printf("(undefined)n");
63 else {
64 switch(ptr->opt_level) {
65 case SOL_SOCKET:
66 case IPPROTO_IP:
67 case IPPROTO_TCP:
68 fd = Socket(AF_INET, SOCK_STREAM, 0);
69 break;
70 #ifdef IPV6
71 case IPPROTO_IPV6:
72 fd = Socket(AF_INET6, SOCK_STREAM, 0);
73 break;
74 #endif
75 #ifdef IPPROTO_SCTP
76 case IPPROTO_SCTP:
77 fd = Socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
78 break;
79 #endif
80 default:
81 err_quit("Can't create fd for level %dn", ptr->opt_level);
82 }
83 len = sizeof(val);
84 if (getsockopt(fd, ptr->opt_level, ptr->opt_name,
85 &val, &len) == -1) {
86 err_ret("getsockopt error");
87 } else {
88 printf("default = %sn", (*ptr->opt_val_str)(&val, len));
89 }
90 close(fd);
91 }
92 }
93 exit(0);
94 }
Перебор всех параметров
59-63
Мы перебираем все элементы нашего массива. Если указатель opt_val_str
пустой, то параметр не определен реализацией (что, как мы показали, возможно для SO_REUSEPORT
).
Создание сокета
63-82
Мы создаем сокет, на котором проверяем действие параметров. Для проверки параметров сокета и уровней IPv4 и TCP мы используем сокет IPv4 TCP. Для проверки параметров сокетов уровня IPv6 мы используем сокет IPv6 TCP, а для проверки параметров SCTP – сокет IPv4 SCTP.
Вызов функции getsockopt
83-87
Мы вызываем функцию getsockopt
, но не завершаем ее выполнение, если возвращается ошибка. Многие реализации определяют имена некоторых параметров сокетов, даже если не поддерживают эти параметры. Неподдерживаемые параметры выдают ошибку ENOPROTOOPT
.
Вывод значения параметра по умолчанию
88-89
Если функция getsockopt
успешно завершается, мы вызываем нашу функцию для преобразования значения параметра в строку и выводим эту строку.
В листинге 7.1 мы показали четыре прототипа функций, по одному для каждого типа возвращаемого значения параметра. В листинге 7.3 показана одна из этих функций, sock_str_flag
, которая выводит значение параметра, являющегося флагом. Другие три функции аналогичны этой.
Листинг 7.3. Функция sock_str_flag: преобразование флага в строку
//sockopt/checkopts.с
95 static char strres[128];
96 static char *
97 sock_str_flag(union val *ptr, int len)
98 {
99 if (len != sizeof(int))
100 snprint(strres, sizeof(strres), "size (%d) not sizeof(int)", len);
101 else
102 snprintf(strres, sizeof(strres),
103 "%s", (ptr->i_val == 0) ? "off" : "on");
104 return(strres);
105 }
99-104
Вспомните, что последний аргумент функции getsockopt
– это аргумент типа «значение-результат». Первое, что мы проверяем, – это то, что размер значения, возвращаемого функцией getsockopt
, совпадает с предполагаемым. В зависимости от того, является ли значение флага нулевым или нет, возвращается строка off
или on
.
Выполнение этой программы под FreeBSD 4.8 с пакетами обновлений KAME SCTP дает следующий вывод:
freebsd % checkopts
SO_BROADCAST: default = off
SO_DEBUG: default = off
SO_DONTROUTE: default = off
SO_ERROR: default = 0
SO_KEEPALIVE: default = off
SO_LINGER: default = l_onoff = 0, l_linger = 0
SO_OOBINLINE: default = off
SO_RCVBUF: default = 57344
SO_SNDBUF: default = 32768
SO_RCVLOWAT: default = 1
SO_SNDLOWAT: default = 2048
SO_RCVTIMEO: default = 0 sec, 0 usec
SO_SNDTIMEO: default = 0 sec, 0 usec
SO_REUSEADDR: default = off
SO_REUSEPORT: default = off
SO_TYPE: default = 1
SO_USELOOPBACK: default = off
IP_TOS: default = 0
IP_TTL: default = 64
IPV6_DONTFRAG: default = off
IPV6_UNICAST_HOPS: default = -1
IPV6_V6ONLY: default = off
TCP_MAXSEG: default = 512
TCP_NODELAY: default = off
SCTP_AUTOCLOSE: default = 0
SCTP_MAXBURST: default = 4
SCTP_MAXSEG: default = 1408
SCTP_NODELAY: default = off
Значение 1, возвращаемое для параметра SO_TYPE
, для этой реализации соответствует SOCK_STREAM
.