KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программное обеспечение » Уильям Стивенс - UNIX: разработка сетевых приложений

Уильям Стивенс - UNIX: разработка сетевых приложений

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Уильям Стивенс, "UNIX: разработка сетевых приложений" бесплатно, без регистрации.
Перейти на страницу:

А что произойдет в случае сокета UDP, когда в приемном буфере имеется дейтаграмма? При вызове recvfrom с флагом MSG_PEEK, за которым последует другой вызов без задания MSG_PEEK, возвращаемые значения обоих вызовов (размер дейтаграммы, ее содержимое и адрес отправителя) будут совпадать, даже если в приемный буфер сокета между двумя вызовами добавляются дополнительные дейтаграммы. (Мы считаем, конечно, что никакой другой процесс не использует тот же дескриптор и не осуществляет чтение из данного сокета в это же время.)

3. Некоторые реализации поддерживают команду FIONREAD функции ioctl. Третий аргумент функции ioctl — это указатель на целое число, а возвращаемое в этом целом числе значение — это текущее число байтов в приемном буфере сокета [128, с. 553]. Это значение является общим числом установленных в очередь байтов, которое для сокета UDP включает все дейтаграммы, установленные в очередь. Также помните о том, что значение, возвращаемое для сокета UDP, в Беркли-реализациях включает пространство, требуемое для структуры адреса сокета, содержащей IP-адрес отправителя и порт для каждой дейтаграммы (16 байт для IP4, 24 байта для IP6).

14.8. Сокеты и стандартный ввод-вывод

Во всех наших примерах мы применяли то, что иногда называется вводом-выводом Unix, вызывали функции read и write и их разновидности (recv, send и т.д.). Эти функции работают с дескрипторами и обычно реализуются как системные вызовы внутри ядра Unix.

Другой метод выполнения ввода-вывода заключается в использовании стандартной библиотеки ввода-вывода. Она задается стандартом ANSI С и была задумана как библиотека, совместимая с не-Unix системами, поддерживающими ANSI С. Стандартная библиотека ввода-вывода обрабатывает некоторые моменты, о которых мы должны заботиться сами при использовании функций ввода- вывода Unix, таких как автоматическая буферизация потоков ввода и вывода. К сожалению, ее обработка буферизации потока может представить новый ряд проблем, о которых следует помнить. Глава 5 [110] подробно описывает стандартную библиотеку ввода-вывода, а в [92] представлена полная реализация стандартной библиотеки ввода-вывода и ее обсуждение.

ПРИМЕЧАНИЕ

При обсуждении стандартной библиотеки ввода-вывода используется термин «поток» в выражениях типа «мы открываем поток ввода» или «мы очищаем поток вывода». Не путайте это с подсистемой потоков STREAMS, которую мы обсуждаем в главе 31.

Стандартная библиотека ввода-вывода может использоваться с сокетами, но есть несколько моментов, которые необходимо при этом учитывать.

■ Стандартный поток ввода-вывода может быть создан из любого дескриптора при помощи вызова функции 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 % tcpcli02 206.168.112.96

hello, world мы набираем эту строку, но не получаем отражения

and hi       и на эту строку нет ответа

hello??      и на эту строку нет ответа

^D           наш символ конца файла

hello, world затем выводятся три отраженные строки

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 получает символ конца файла на своем сокете, и клиент завершает свою работу.

Проблема здесь заключается в том, что буферизация на стороне сервера выполняется автоматически стандартной библиотекой ввода-вывода. Существует три типа буферизации, выполняемой стандартной библиотекой ввода-вывода.

1. Полная буферизация (fully buffered) означает, что ввод-вывод имеет место, только когда буфер заполнен, процесс явно вызывает функцию fflush или процесс завершается посредством вызова функции exit. Обычный размер стандартного буфера ввода-вывода — 8192 байта.

2. Буферизация по строкам (line buffered) означает, что ввод-вывод имеет место, только когда встречается символ перевода строки, процесс вызывает функцию fflush или процесс завершается вызовом функции exit.

3. Отсутствие буферизации (unbuffered) означает, что ввод-вывод имеет место каждый раз, когда вызывается функция стандартного ввода-вывода.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*