Стандартная библиотека ввода-вывода может использоваться с сокетами, но есть несколько моментов, которые необходимо при этом учитывать.
■ Стандартный поток ввода-вывода может быть создан из любого дескриптора при помощи вызова функции
fdopen
. Аналогично, имея стандартный поток ввода-вывода, мы можем получить соответствующий дескриптор, вызывая функцию
fileno
. С функцией
fileno
мы впервые встретились в листинге 6.1, когда мы хотели вызвать функцию
select
для стандартного потока ввода-вывода. Функция
select
работает только с дескрипторами, поэтому нам необходимо было получить дескриптор для стандартного потока ввода-вывода.
■ Сокеты TCP и UDP являются двусторонними. Стандартные потоки ввода- вывода также могут быть двусторонними: мы просто открываем поток типа
r+
, что означает чтение-запись. Но в таком потоке за функцией вывода не может следовать функция ввода, если между ними нет вызова функции
fflush
,
fseek
,
fsetpots
или
rewind
. Аналогично, за функцией вывода не может следовать функция ввода, если между ними нет вызова функции
fseek
,
fsetpots
,
rewind
, в том случае, когда при вводе не получен признак конца файла. Проблема с последними тремя функциями состоит в том, что все они вызывают функцию
lseek
, которая не работает с сокетами.
■ Простейший способ обработки подобной проблемы чтения-записи — это открытие двух стандартных потоков ввода-вывода для данного сокета: одного для чтения и другого для записи.
Пример: функция str_echo, использующая стандартный ввод-вывод
Сейчас мы модифицируем наш эхо-сервер TCP (см. листинг 5.2) для использования стандартного ввода-вывода вместо функций
readline
и
writen
. В листинге 14.6 представлена версия нашей функции
str_echo
, использующая стандартный ввод-вывод. (С этой версией связана проблема, которую мы вскоре опишем.)
Листинг 14.6. Функция str_echo, переписанная с использованием стандартного ввода-вывода
//advio/str_echo_stdiо02.с
1 #include "unp.h"
2 void
3 str_echo(int sockfd)
4 {
5 char line[MAXLINE];
6 FILE *fpin, *fpout;
7 fpin = Fdopen(sockfd, "r");
8 fpout = Fdopen(sockfd, "w");
9 while (Fgets(line, MAXLINE, fpin) != NULL)
10 Fputs(line, fpout);
11 }
Преобразование дескриптора в поток ввода и поток вывода
7-10
Функцией
fdopen
создаются два стандартных потока ввода-вывода: один для ввода и другой для вывода. Вызовы функций
readline
и
writen
заменены вызовами функций
fgets
и
fputs
.
Если мы запустим наш сервер с этой версией функции
str_echo
и затем запустим наш клиент, мы увидим следующее:
hpux % <b>tcpcli02 206.168.112.96</b>
<b>hello, world</b> <i>мы набираем эту строку, но не получаем отражения</i>
<b>and hi</b> <i>и на эту строку нет ответа</i>
<b>hello??</b> <i>и на эту строку нет ответа</i>
<b>^D</b> <i>наш символ конца файла</i>
hello, world <i>затем выводятся три отраженные строки</i>
and hi
hello??
Здесь возникает проблема буферизации, поскольку сервер ничего не отражает, пока мы не введем наш символ конца файла. Выполняются следующие шаги:
■ Мы набираем первую строку ввода, и она отправляется серверу.
■ Сервер читает строку с помощью функции
fgets
и отражает ее с помощью функции
fputs
.
■ Но стандартный поток ввода-вывода сервера полностью буферизован стандартной библиотекой ввода-вывода. Это значит, что библиотека копирует отраженную строку в свой стандартный буфер ввода-вывода для этого потока, но не выдает содержимое буфера в дескриптор, поскольку буфер не заполнен.
■ Мы набираем вторую строку ввода, и она отправляется серверу.
■ Сервер читает строку с помощью функции
fgets
и отражает ее с помощью функции
fputs
.
■ Снова стандартная библиотека ввода-вывода сервера только копирует строку в свой буфер, но не выдает содержимое буфера в дескриптор, поскольку он не заполнен.
■ По тому же сценарию вводится третья строка.
■ Мы набираем наш символ конца файла, и функция
str_cli
(см. листинг 6.2) вызывает функцию
shutdown
, посылая серверу сегмент FIN.
■ TCP сервера получает сегмент FIN, который читает функция
fgets
, в результате чего функция
fgets
возвращает пустой указатель.
■ Функция
str_echo
возвращает серверу функцию
main
(см. листинг 5.9), и дочерний процесс завершается при вызове функции
exit
.
■ Библиотечная функция
exit
языка С вызывает стандартную функцию очистки ввода-вывода [110, с. 162-164], и буфер вывода, который был частично заполнен нашими вызовами функции
fputs
, теперь выводит скопившиеся в нем данные.
■ Дочерний процесс сервера завершается, в результате чего закрывается его присоединенный сокет, клиенту отсылается сегмент FIN и заканчивается последовательность завершения соединения TCP.
■ Наша функция
str_cli
получает и выводит три отраженных строки.
■ Затем функция
str_cli
получает символ конца файла на своем сокете, и клиент завершает свою работу.
Проблема здесь заключается в том, что буферизация на стороне сервера выполняется автоматически стандартной библиотекой ввода-вывода. Существует три типа буферизации, выполняемой стандартной библиотекой ввода-вывода.