Уильям Стивенс - UNIX: разработка сетевых приложений
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.
6.12. Резюме
В 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. Мы говорили, что набор дескрипторов можно присвоить другому набору дескрипторов, используя оператор присваивания языка С. Как это сделать, если набор дескрипторов является массивом целых чисел? (Подсказка: посмотрите на свой системный заголовочный файл <sys/select.h> или <sys/types.h>.)
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.