Рис. 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)
1
Все исходные коды программ, опубликованные в этой книге, вы можете найти по адресу http://www.piter.com.
//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 }
Вызов функции select
8-13
Нам нужен только один набор дескрипторов — для проверки готовности сокета для чтения. Этот набор дескрипторов инициализируется макросом
FD_ZERO
, после чего с помощью макроса
FD_SET
устанавливаются два бита: бит, соответствующий указателю файла
fp
стандартного потока ввода-вывода, и бит, соответствующий дескриптору сокета
sockfd
.
Функция
fileno
преобразует указатель файла стандартного потока ввода-вывода в соответствующий ему дескриптор. Функция
select
(а также
poll
) работает только с дескрипторами.
Функция
select
вызывается после определения максимального из двух дескрипторов. В этом вызове указатель на набор дескрипторов для записи и указатель на набор дескрипторов с исключениями являются пустыми. Последний аргумент (ограничение по времени) также является пустым указателем, поскольку мы хотим, чтобы процесс был блокирован, пока не будут готовы данные для чтения.
Обработка сокета, готового для чтения
14-18
Если по завершении функции
select
сокет готов для чтения, отраженная строка считывается функцией
readline
и выводится функцией
fputs
.
Обработка ввода, допускающего возможность чтения
19-23
Если стандартный поток ввода готов для чтения, строка считывается функцией
fgets
и записывается в сокет с помощью функции
writen
.
Обратите внимание, что используются те же четыре функции ввода-вывода, что и в листинге 5.4:
fgets
,
writen
,
readline
и
fputs
, но порядок их следования внутри функции
str_cli
изменился. Раньше выполнение функции
str_cli
определялось функцией
fgets
, а теперь ее место заняла
select
. С помощью всего нескольких дополнительных строк кода (сравните листинги 6.1 и 5.4) мы значительно увеличили устойчивость клиента.
6.5. Пакетный ввод
К сожалению, наша функция
str_cli
все еще не вполне корректна. Сначала вернемся к ее исходной версии, приведенной в листинге 5.4. Эта функция работает в режиме остановки и ожидания (stop-and-wait mode), что удобно для интерактивного использования: функция отправляет строку серверу и затем ждет его ответа. Время ожидания складывается из одного периода обращения (RTT) и времени обработки сервером (которое близко к нулю в случае простого эхо-сервера). Следовательно, мы можем предположить, сколько времени займет отражение данного числа строк, если мы знаем время обращения (RTT) между клиентом и сервером.
Измерить RTT позволяет утилита
ping
. Если мы измерим с ее помощью время обращения к
connix.com
с нашего узла
solaris
, то средний период RTT после 30 измерений будет равен 175 мс. В [111, с. 89] показано, что это справедливо для дейтаграммы IP длиной 84 байт. Если мы возьмем первые 2000 строк файла
termcap
Solaris 2.5, то итоговый размер файла будет равен 98 349 байт, то есть в среднем 49 байт на строку. Если мы добавим размеры заголовка IP (20 байт) и заголовка TCP (20 байт), то средний сегмент TCP будет составлять 89 байт, почти как размер пакета утилиты
ping
. Следовательно, мы можем предположить, что общее время составит около 350 с для 2000 строк (2000x0,175 с). Если мы запустим наш эхо-клиент TCP из главы 5, действительное время получится около 354 с, что очень близко к нашей оценке.