Некоторые производители изменяют свои реализации функции
select
, с тем чтобы позволить процессу задавать значение
FD_SETSIZE
, превышающее значение по умолчанию. BSD/OS также изменила реализацию ядра, чтобы допустить большие наборы дескрипторов, кроме того, в ней добавлено четыре новых макроопределения
FD_
для динамического размещения больших наборов дескрипторов в памяти и для работы с ними. Однако с точки зрения переносимости не стоит злоупотреблять использованием больших наборов дескрипторов.
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
вызывается после определения максимального из двух дескрипторов. В этом вызове указатель на набор дескрипторов для записи и указатель на набор дескрипторов с исключениями являются пустыми. Последний аргумент (ограничение по времени) также является пустым указателем, поскольку мы хотим, чтобы процесс был блокирован, пока не будут готовы данные для чтения.
14-18
Если по завершении функции
select
сокет готов для чтения, отраженная строка считывается функцией
readline
и выводится функцией
fputs
.
19-23
Если стандартный поток ввода готов для чтения, строка считывается функцией
fgets
и записывается в сокет с помощью функции
writen
.