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

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

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

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);


22  stdineof = 0;

23  for (;;) {

24   /* блокирование до готовности сокета */

25   dopoll.dp_timeout = -1;

26   dopoll.dp_nfds = 2;

27   dopoll.dp_fds = pollfd;

28   result = Ioctl(wfd, DP_POLL, &dopoll);


29   /* цикл по готовым дескрипторам */

30   for (i = 0; i < result; i++) {

31    if (dopoll.dp_fds[i].fd == sockfd) {

32     /* сокет готов к чтению */

33     if ((n = Read(sockfd, buf, MAXLINE)) == 0) {

34      if (stdineof == 1)

35       return; /* нормальное завершение */

36      else

37       err_quit("str_cli: server terminated prematurely");

38     }


39     Write(fileno(stdout), buf, n);

40    } else {

41     /* дескриптор готов к чтению */

42     if ((n = Read(fileno(fp), buf, MAXLINE)) == 0) {

43      stdineof = 1;

44      Shutdown(sockfd, SHUT_WR); /* отправка FIN */

45      continue;

46     }


47     Writen(sockfd, buf, n);

48    }

49   }

50  }

51 }

Составление списка дескрипторов для /dev/poll

14-21 Заполнив массив структур pollfd, мы передаем его в /dev/poll. В нашем примере используются только два файловых дескриптора, так что мы помещаем их в статический массив. На практике программы, использующие /dev/poll, обычно следят за сотнями или даже тысячами дескрипторов одновременно, поэтому массив выделяется динамически.

Ожидание данных

24-28 Программа не вызывает select, а блокируется в вызове ioctl в ожидании поступления данных. Возвращаемое значение представляет собой количество готовых к чтению дескрипторов файлов.

Цикл по дескрипторам

30-49 Наша программа относительно проста, потому что мы знаем, что дескрипторов всего два. В большой программе цикл будет более сложным. Возможно даже разделение программы на потоки для обработки данных, полученных по разным дескрипторам.

Интерфейс kqueue

Система FreeBSD версии 4.1 предложила сетевым программистам новый интерфейс, получивший название kqueue. Этот интерфейс позволяет процессу зарегистрировать фильтр событий, описывающий интересующие данный процесс события kqueue. К событиям этого типа относятся операции ввода-вывода с файлами и тайм-ауты, а также асинхронный ввод-вывод, уведомление об изменении файлов и обработка сигналов.

#include <sys/types.h>

#include <sys/event.h>

#include <sys/time.h>


int kqueue(void);

int kevent(int kq, const struct kevent *changelist, int nchanges,

 struct kevent *eventlist, int nevents, const struct timespec *timeout);

void EV_SET(struct kevent *kev, uintptr_t ident, short filter,

 u_short flags, u_int fflags, intptr_t data, void *udata);

Функция kqueue возвращает новый дескриптор kqueue, который может использоваться в последующих вызовах kevent. Функция kevent применяется для регистрации интересующих событий, а также для получения уведомлений об этих событиях. Параметры changelist и nchanges описывают изменения в предыдущем варианте списка событий. Если nchanges отлично от нуля, выполняются все запрошенные в структуре changelist изменения. Функция kevent возвращает количество событий или нуль, если произошел выход по тайм-ауту. В аргументе timeout хранится значение тайм-аута, обрабатываемое подобно тому, как при вызове select (NULL для блокирования, ненулевое значение для задания конкретного тайм- аута, а нулевое значение трактуется как необходимость неблокирующего вызова). Обратите внимание, что параметр timeout имеет тип struct timespec, отличающийся от struct timeval в вызове select тем, что первый имеет наносекундное разрешение, а второй — микросекундное.

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