Страница 66 из 399
Многие реализации имеют объявления, аналогичные приведенному ниже, которое взято из заголовочного файла 4.4BSD <sys/types.h>:
/*
Значение FD_SETSIZE может быть определено пользователем,
но заданное здесь по умолчанию
является достаточным в большинстве случаев.
*/
#ifndef FD_SETSIZE
#define FD_SETSIZE 256
#endif
Исходя из этого комментария, можно подумать, что если перед подключением этого заголовочного файла присвоить FD_SETSIZE значение, превышающее 256, то увеличится размер наборов дескрипторов, используемых функцией select. К сожалению, обычно это не действует.
Чтобы понять, в чем дело, обратите внимание, что на рис. 16.53 [128] объявляются три набора дескрипторов внутри ядра, а в качестве верхнего предела используется определенное в ядре значение FD_SETSIZE. Единственный способ увеличить размер наборов дескрипторов — это увеличить значение FD_SETSIZE и затем перекомпилировать ядро. Изменения значения без перекомпиляции ядра недостаточно.
Некоторые производители изменяют свои реализации функции select, с тем чтобы позволить процессу задавать значение FD_SETSIZE, превышающее значение по умолчанию. BSD/OS также изменила реализацию ядра, чтобы допустить большие наборы дескрипторов, кроме того, в ней добавлено четыре новых макроопределения FD_<i>xxx</i> для динамического размещения больших наборов дескрипторов в памяти и для работы с ними. Однако с точки зрения переносимости не стоит злоупотреблять использованием больших наборов дескрипторов.
6.4. Функция str_cli (продолжение)
Теперь мы можем переписать нашу функцию str_cli, представленную в разделе 5.5 (на этот раз используя функцию select), таким образом, чтобы мы получали уведомление, как только завершится процесс сервера. Проблема с предыдущей версией состояла в том, что процесс мог оказаться заблокированным в вызове функции fgets, когда что-то происходило на сокете. Наша новая версия этой функции вместо этого блокируется в вызове функции select, ожидая готовности для чтения либо стандартного потока ввода, либо сокета. На рис. 6.7 показаны различные условия, обрабатываемые с помощью вызова функции select.
Рис. 6.7. Условия, обрабатываемые функцией select в вызове функции str_cli
Сокет обрабатывает три условия:
1. Если протокол TCP собеседника отправляет данные, сокет становится готовым для чтения, и функция read возвращает положительное значение (то есть число байтов данных).
2. Если протокол TCP собеседника отправляет сегмент FIN (процесс завершается), сокет становится готовым для чтения, и функция read возвращает нуль (признак конца файла).
3. Если TCP собеседника отправляет RST (узел вышел из строя и перезагрузился), сокет становится готовым для чтения, и функция read возвращает -1, а переменная errno содержит код соответствующей ошибки.
В листинге 6.1[1] представлен исходный код этой версии функции.
Листинг 6.1. Реализация функции str_cli с использованием функции select (усовершенствованный вариант находится в листинге 6.2)
//select/strcliselect01.c
1 #include "unp.h"
2 void
3 str_cli(FILE *fp, int sockfd)
4 {
5 int maxfdp1;
6 fd_set rset;
7 char sendline[MAXLINE], recvline[MAXLINE];
8 FD_ZERO(&rset);
9 for (;;) {
10 FD_SET(fileno(fp), &rset);
11 FD_SET(sockfd, &rset);
12 maxfdp1 = max(fileno(fp), sockfd) + 1;
13 Select(maxfdp1, &rset, NULL, NULL, NULL);
14 if (FD_ISSET(sockfd, &rset)) { /* сокет готов для чтения */
15 if (Readline(sockfd, recvline, MAXLINE) == 0)
16 err_quit("str_cli: server terminated prematurely");
17 Fputs(recvline, stdout);
18 }
19 if (FD_ISSET(fileno(fp), &rset)) { /* входное устройство готово для
чтения */
20 if (Fgets(sendline, MAXLINE, fp) == NULL)
21 return; /* все сделано */
22 Writen(sockfd, sendline, strlen(sendline));
23 }
24 }
25 }
8-13 Нам нужен только один набор дескрипторов — для проверки готовности сокета для чтения. Этот набор дескрипторов инициализируется макросом FD_ZERO, после чего с помощью макроса FD_SET устанавливаются два бита: бит, соответствующий указателю файла fp стандартного потока ввода-вывода, и бит, соответствующий дескриптору сокета sockfd. Функция fileno преобразует указатель файла стандартного потока ввода-вывода в соответствующий ему дескриптор. Функция select (а также poll) работает только с дескрипторами.
Функция select вызывается после определения максимального из двух дескрипторов. В этом вызове указатель на набор дескрипторов для записи и указатель на набор дескрипторов с исключениями являются пустыми. Последний аргумент (ограничение по времени) также является пустым указателем, поскольку мы хотим, чтобы процесс был блокирован, пока не будут готовы данные для чтения.
1
Все исходные коды программ, опубликованные в этой книге, вы можете найти по адресу http://www.piter.com.