UNIX: разработка сетевых приложений
ИнициализацияМы используем первый элемент в массиве20-24для прослушиваемого сокета и присваиваем дескрипторам для оставшихся элементов -1. Мы также задаем в качестве аргумента функцииclientсобытиеpoll, чтобы получить уведомление от этой функции в том случае, когда новое соединение будет готово к приему. ПеременнаяPOLLRDNORMсодержит максимальный индекс массиваmaxi, используемый в настоящий момент.clientВторая часть нашей функции приведена в листинге 6.6.
Листинг 6.6. Вторая часть сервера TCP, использующего функцию poll
//tcpcliserv/tcpservpoll01.c25 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 } else53 err_sys("readline error");54 } else if (n == 0) {55 /* соединение закрыто клиентом */56 Close(sockfd);57 client[i].fd = -1;58 } else59 Writen(sockfd, line, n);60 if (--nready <= 0)61 break; /* больше нет дескрипторов, готовых для чтения */62 }63 }64 }65 }Вызов функции poll, проверка нового соединенияМы вызываем функцию26-42для ожидания нового соединения либо данных на существующем соединении. Когда новое соединение принято, мы находим первый свободный элемент в массивеpoll— это первый элемент с отрицательным дескриптором. Обратите внимание, что мы начинаем поиск с индекса 1, поскольку элементclientиспользуется для прослушиваемого сокета. Когда свободный элемент найден, мы сохраняем дескриптор и устанавливаем событиеclient[0].POLLRDNORMПроверка данных на существующем соединенииДва события, которые нас интересуют, — это43-63иPOLLRDNORM. Второй флаг в элементеPOLLERRмы не устанавливали, поскольку этот флаг возвращается всегда, если соответствующее условие выполнено. Причина, по которой мы проверяем событиеevent, в том, что некоторые реализации возвращают это событие, когда приходит сегмент RST, другие же в такой ситуации возвращают событиеPOLLERR. В любом случае мы вызываем функциюPOLLRDNORM, и если произошла ошибка, эта функция возвратит ее. Когда существующее соединение завершается клиентом, мы просто присваиваем элементуreadзначение -1.fd6.12. Резюме
В Unix существует пять различных моделей ввода-вывода:
■ блокируемый ввод-вывод;
■ неблокируемый ввод-вывод;
■ мультиплексирование ввода-вывода;
■ управляемый сигналом ввод-вывод;
■ асинхронный ввод-вывод.
По умолчанию используется блокируемый ввод-вывод, и этот вариант встречается наиболее часто. Неблокируемый ввод-вывод и управляемый сигналом ввод-вывод мы рассмотрим в последующих главах. В этой главе мы рассмотрели мультиплексирование ввода-вывода. Асинхронный ввод-вывод определяется в стандарте POSIX, но поддерживающих его реализаций не так много.
Наиболее часто используемой функцией для мультиплексирования ввода- вывода является функция
. Мы сообщаем этой функции, какие дескрипторы нас интересуют (для чтения, записи или условия ошибки), а также передаем ей максимальное время ожидания и максимальное число дескрипторов (увеличенное на единицу). Большинство вызовов функцииselectопределяют количество дескрипторов, готовых для чтения, и, как мы отметили, единственное условие исключения при работе с сокетами — это прибытие внеполосных данных (см. главу 21). Поскольку функцияselectпозволяет ограничить время блокирования функции, мы используем это свойство в листинге 14.3 для ограничения по времени операции ввода.selectИспользуя эхо-клиент в пакетном режиме с помощью функции
, мы выяснили, что даже если обнаружен признак конца файла, данные все еще могут находиться в канале на пути к серверу или от сервера. Обработка этого сценария требует применения функцииselect, которая позволяет воспользоваться таким свойством TCP, как возможность половинного закрытия соединения (half-close feature).shutdown