UNIX: разработка сетевых приложений
Стандартная библиотека ввода-вывода может использоваться с сокетами, но есть несколько моментов, которые необходимо при этом учитывать.
■ Стандартный поток ввода-вывода может быть создан из любого дескриптора при помощи вызова функции
. Аналогично, имея стандартный поток ввода-вывода, мы можем получить соответствующий дескриптор, вызывая функциюfdopen. С функциейfilenoмы впервые встретились в листинге 6.1, когда мы хотели вызвать функциюfilenoдля стандартного потока ввода-вывода. Функцияselectработает только с дескрипторами, поэтому нам необходимо было получить дескриптор для стандартного потока ввода-вывода.select■ Сокеты TCP и UDP являются двусторонними. Стандартные потоки ввода- вывода также могут быть двусторонними: мы просто открываем поток типа
, что означает чтение-запись. Но в таком потоке за функцией вывода не может следовать функция ввода, если между ними нет вызова функцииr+,fflush,fseekилиfsetpots. Аналогично, за функцией вывода не может следовать функция ввода, если между ними нет вызова функцииrewind,fseek,fsetpots, в том случае, когда при вводе не получен признак конца файла. Проблема с последними тремя функциями состоит в том, что все они вызывают функциюrewind, которая не работает с сокетами.lseek■ Простейший способ обработки подобной проблемы чтения-записи — это открытие двух стандартных потоков ввода-вывода для данного сокета: одного для чтения и другого для записи.
Пример: функция str_echo, использующая стандартный ввод-вывод
Сейчас мы модифицируем наш эхо-сервер TCP (см. листинг 5.2) для использования стандартного ввода-вывода вместо функций
иreadline. В листинге 14.6 представлена версия нашей функцииwriten, использующая стандартный ввод-вывод. (С этой версией связана проблема, которую мы вскоре опишем.)str_echoЛистинг 14.6. Функция str_echo, переписанная с использованием стандартного ввода-вывода
//advio/str_echo_stdiо02.с1 #include "unp.h"2 void3 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_echohpux % <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 hihello??Здесь возникает проблема буферизации, поскольку сервер ничего не отражает, пока мы не введем наш символ конца файла. Выполняются следующие шаги:
■ Мы набираем первую строку ввода, и она отправляется серверу.
■ Сервер читает строку с помощью функции
и отражает ее с помощью функцииfgets.fputs■ Но стандартный поток ввода-вывода сервера полностью буферизован стандартной библиотекой ввода-вывода. Это значит, что библиотека копирует отраженную строку в свой стандартный буфер ввода-вывода для этого потока, но не выдает содержимое буфера в дескриптор, поскольку буфер не заполнен.
■ Мы набираем вторую строку ввода, и она отправляется серверу.
■ Сервер читает строку с помощью функции
и отражает ее с помощью функцииfgets.fputs■ Снова стандартная библиотека ввода-вывода сервера только копирует строку в свой буфер, но не выдает содержимое буфера в дескриптор, поскольку он не заполнен.
■ По тому же сценарию вводится третья строка.
■ Мы набираем наш символ конца файла, и функция
(см. листинг 6.2) вызывает функциюstr_cli, посылая серверу сегмент FIN.shutdown■ TCP сервера получает сегмент FIN, который читает функция
, в результате чего функцияfgetsвозвращает пустой указатель.fgets■ Функция
возвращает серверу функциюstr_echo(см. листинг 5.9), и дочерний процесс завершается при вызове функцииmain.exit■ Библиотечная функция
языка С вызывает стандартную функцию очистки ввода-вывода [110, с. 162-164], и буфер вывода, который был частично заполнен нашими вызовами функцииexit, теперь выводит скопившиеся в нем данные.fputs■ Дочерний процесс сервера завершается, в результате чего закрывается его присоединенный сокет, клиенту отсылается сегмент FIN и заканчивается последовательность завершения соединения TCP.
■ Наша функция
получает и выводит три отраженных строки.str_cli■ Затем функция
получает символ конца файла на своем сокете, и клиент завершает свою работу.str_cliПроблема здесь заключается в том, что буферизация на стороне сервера выполняется автоматически стандартной библиотекой ввода-вывода. Существует три типа буферизации, выполняемой стандартной библиотекой ввода-вывода.