1. Полная буферизация (fully buffered) означает, что ввод-вывод имеет место, только когда буфер заполнен, процесс явно вызывает функцию
fflush
или процесс завершается посредством вызова функции
exit
. Обычный размер стандартного буфера ввода-вывода — 8192 байта.
2. Буферизация по строкам (line buffered) означает, что ввод-вывод имеет место, только когда встречается символ перевода строки, процесс вызывает функцию
fflush
или процесс завершается вызовом функции
exit
.
3. Отсутствие буферизации (unbuffered) означает, что ввод-вывод имеет место каждый раз, когда вызывается функция стандартного ввода-вывода.
Большинство реализаций Unix стандартной библиотеки ввода-вывода используют следующие правила:
■ Стандартный поток ошибок никогда не буферизуется.
■ Стандартные потоки ввода и вывода буферизованы полностью, если они не подключены к терминальному устройству, в противном случае они буферизуются по строкам.
■ Все остальные потоки тоже буферизованы полностью, если они не подключены к терминалу, в случае чего они буферизованы по строкам.
Поскольку сокет не является терминальным устройством, проблема, отмеченная с нашей функцией
str_echo
в листинге 14.6, заключается в том, что поток вывода (
fpot
) полностью буферизован. Есть два решения: мы можем сделать поток вывода буферизованным по строкам при помощи вызова функции
setvbuf
либо заставить каждую отраженную строку выводиться при помощи вызова функции
fflush
после каждого вызова функции
fputs
. Применение любого из этих изменений скорректирует поведение нашей функции
str_echo
. На практике оба варианта чреваты ошибками и могут плохо взаимодействовать с алгоритмом Нагла. В большинстве случаев оптимальным решением будет отказаться от использования стандартной библиотеки ввода-вывода для сокетов и работать с буферами, а не со строками (см. раздел 3.9). Использование стандартных функций ввода-вывода имеет смысл в тех случаях, когда потенциальный выигрыш перевешивает затруднения.
ПРИМЕЧАНИЕ
Будьте осторожны — некоторые реализации стандартной библиотеки ввода-вывода все еще вызывают проблемы при работе с дескрипторами, большими 255. Эта проблема может возникнуть с сетевыми серверами, обрабатывающими множество дескрипторов. Проверьте определение структуры FILE в вашем заголовочном файле <stdio.h>, чтобы увидеть, к какому типу переменных относится дескриптор.
14.9. Расширенный опрос
В начале этой главы мы рассказывали о способах установки таймеров для операций с сокетами. Во многих операционных системах для этого существуют функции
poll
и
select
, которые были описаны в главе 6. Ни один из этих методов еще не стандартизован POSIX, поэтому между реализациями существуют определенные различия. Код, использующий подобные механизмы, должен считаться непереносимым. Мы рассмотрим два механизма, прочие весьма похожи на них.
Интерфейс /dev/poll
В Solaris имеется специальный файл
/dev/poll
, с помощью которого можно опрашивать большее количество дескрипторов файлов. Проблема
select
и
poll
состоит в том, что список дескрипторов приходится передавать при каждом вызове. Устройство опроса поддерживает информацию о состоянии между вызовами, так что программа может подготовить список подлежащих опросу дескрипторов, а потом спокойно зациклиться в опросе и не заполнять список каждый раз.
После открытия
/dev/poll
программа должна инициализировать массив структур
pollfd
(тех же, которые используются функцией
poll
, но в этом случае поле
revents
не используется). Затем массив передается ядру вызовом
write
(структура записывается непосредственно в
/dev/poll
). После этого программа может вызывать
ioctl DP_POLL
и ждать событий. При вызове
ioctl
передается следующая структура:
struct dvpoll {
struct pollfd* dp_fds;
int dp_nfds;
int dp_timeout;
};
Поле
dp_fds
указывает на буфер, используемый для хранения массива структур
pollfd
, возвращаемых вызовом
ioctl
. Поле
dp_nfds
задает размер буфера. Вызов
ioctl
блокируется до появления интересующих программу событий на любом из опрашиваемых дескрипторов, или до прохождения
dp_timeout
миллисекунд. При нулевом значении тайм-аута функция
ioctl
возвращается немедленно (то есть данный способ может использоваться для реализации неблокируемых сокетов). Тайм-аут, равный -1, означает неопределенно долгое ожидание.
Измененный код функции
str_cli
, переписанной из листинга 6.2 с использованием
/dev/poll
, приведен в листинге 14.7.
Листинг 14.7. Функция str_cli, использующая /dev/poll
//advio/str_cli_poll03.c
1 #include "unp.h"
2 #include <sys/devpoll.h>
3 void
4 str_cli(FILE *fp, int sockfd)
5 {
6 int stdineof;
7 char buf[MAXLINE];
8 int n;
9 int wfd;
10 struct pollfd pollfd[2];
11 struct dvpoll dopoll;
12 int i;
13 int result;
14 wfd = Open("/dev/poll", O_RDWR, 0);
15 pollfd[0].fd = fileno(fp);
16 pollfd[0].events = POLLIN;
17 pollfd[0].revents = 0;
18 pollfd[1].fd = sockfd;
19 pollfd[1].events = POLLIN;
20 pollfd[1].revents = 0;
21 Write(wfd, pollfd, sizeof(struct pollfd) * 2);