Мы выводим в стандартный поток сообщений об ошибках строку, отмечающую конец файла, вместе с текущим временем. Мы покажем, как мы используем этот вывод, после описания функции. Аналогичные вызовы функции fprintf выполняются неоднократно в процессе выполнения нашей функции.
Возвращение данных функцией read
41-45
Когда функция read возвращает данные, мы увеличиваем на единицу
toiptr
. Мы также включаем бит, соответствующий сокету, в наборе флагов записи, чтобы позже при проверке этого бита в цикле он
был включен и тем самым инициировалась бы попытка записи в сокет с помощью функции
write
.
ПРИМЕЧАНИЕ
Это одно из непростых конструктивных решений, которые приходится принимать при написании кода. У нас есть несколько альтернатив. Вместо установки бита в наборе записи мы можем ничего не делать, и в этом случае функция select будет проверять возможность записи в сокет, когда она будет вызвана в следующий раз. Но это требует дополнительного прохода цикла и вызова функции select, когда мы уже знаем, что у нас есть данные для записи в сокет. Другой вариант — дублировать код, который записывает в сокет, но это кажется расточительным, к тому же это возможный источник ошибки (в случае, если в этой части дублируемого кода есть ошибка и мы обнаруживаем и устраняем ее только в одном месте). Наконец, мы можем создать функцию, записывающую в сокет, и вызывать эту функцию вместо дублирования кода, но эта функция должна использовать три локальные переменные совместно с функцией str_cli, что может привести к необходимости сделать эти переменные глобальными. Выбор, сделанный в нашем случае, — это результат субъективного мнения автора относительно того, какой из описанных трех вариантов предпочтительнее.
Чтение из сокета с помощью функции read
48-64
Эти строки кода аналогичны выражению
if
, только что описанному для случая, когда стандартный поток ввода готов для чтения. Если функция
read
возвращает ошибку
EWOULDBLOCK
, ничего не происходит. Если мы встречаем признак конца файла, присланный сервером, это нормально, когда мы уже получили признак конца файла в стандартном потоке ввода. Но иначе это будет ошибкой, означающей преждевременное завершение работы сервера (
Server terminated prematurely
). Если функция
read
возвращает некоторые данные,
friptr
увеличивается на единицу и в наборе флагов записи включается бит для стандартного потока вывода, с тем чтобы попытаться записать туда данные в следующей части функции.
В листинге 16.3 показана последняя часть нашей функции.
Листинг 16.3. Функция str_cli: третья часть, запись в стандартный поток вывода или сокет
78 if ((nwritten = write(sockfd, tooptr, n)) < 0) {
79 if (errno != EWOULDBLOCK)
80 err_sys("write error to socket");
81 } else {
82 fprintf(stderr, "%s: wrote %d bytes to socket\n",
83 gf_time, nwritten);
84 tooptr += nwritten; /* только что полученное из функции write
число */
85 if (tooptr == toiptr) {
86 toiptr - tooptr = to; /* назад к началу буфера */
87 if (stdineof)
88 Shutdown(sockfd, SHUT_WR); /* посылаем FIN */
89 }
90 }
91 }
92 }
93 }
Запись в стандартный поток вывода с помощью функции write
65-68
Если есть возможность записи в стандартный поток вывода и число байтов для записи больше нуля, вызывается функция
write
. Если возвращается ошибка
EWOULDBLOCK
, ничего не происходит. Обратите внимание, что это условие возможно, поскольку код в конце предыдущей части функции включает бит в наборе флагов записи для стандартного потока вывода, когда не известно, успешно выполнилась функция
write
или нет.
Успешное выполнение функции write
68-74
Если функция
write
выполняется успешно,
froptr
увеличивается на число записанных байтов. Если указатель вывода стал равен указателю ввода, оба указателя переустанавливаются на начало буфера.
Запись в сокет с помощью функции write
76-90
Эта часть кода аналогична коду, только что описанному для записи в стандартный поток вывода. Единственное отличие состоит в том, что когда указатель вывода доходит до указателя ввода, не только оба указателя переустанавливаются в начало буфера, но и появляется возможность отправить серверу сегмент FIN.
Теперь мы проверим работу этой функции и операций неблокируемого ввода-вывода. В листинге 16.4 показана наша функция
gf_time
, вызываемая из функции
str_cli
.
Листинг 16.4. Функция gf_time: возвращение указателя на строку времени