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

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

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

Традиционно System V возвращала для неблокируемой операции ввода-вывода, которую невозможно выполнить, ошибку EAGAIN, в то время как Беркли-реализации возвращали ошибку EWOULDBLOCK. Еще больше дело запутывается тем, что согласно POSIX.1 используется EAGAIN, в то время как в POSIX.1g определено, что используется EWOULDBLOCK. К счастью, большинство систем (включая SVR4 и 4.4BSD) определяют один и тот же код для этих двух ошибок (проверьте свой системный заголовочный файл <sys/errno.h>), поэтому не важно, какой из них использовать. В нашем тексте мы используем ошибку EWOULDBLOCK, как определяется в POSIX.

В разделе 6.2 мы представили различные модели ввода-вывода и сравнили неблокируемый ввод-вывод с другими моделями. В этой главе мы покажем примеры четырех типов операций и разработаем новый тип клиента, аналогичный веб-клиенту, инициирующий одновременно множество соединений TCP при помощи неблокируемой функции connect.

16.2. Неблокируемые чтение и запись: функция str_cli (продолжение)

Мы снова возвращаемся к нашей функции str_cli, которую мы обсуждали в разделах 5.5 и 6.4. Последняя ее версия, задействующая функцию select, продолжает использовать блокируемый ввод-вывод. Например, если в стандартном устройстве ввода имеется некоторая строка, мы читаем ее с помощью функции fgets и затем отправляем серверу с помощью функции writen. Но вызов функции writen может вызвать блокирование процесса, если буфер отправки сокета полон. В то время как мы заблокированы в вызове функции writen, данные могут быть доступны для чтения из приемного буфера сокета. Аналогично, когда строка ввода доступна из сокета, мы можем заблокироваться в последующем вызове функции fputs, если стандартный поток вывода работает медленнее, чем сеть. Наша цель в данном разделе — создать версию этой функции, использующую неблокируемый ввод-вывод. Блокирование будет предотвращено, благодаря чему в это время мы сможем сделать еще что-то полезное.

К сожалению, добавление неблокируемого ввода-вывода значительно усложняет управление буфером функции, поэтому мы будем представлять функцию частями. Мы также заменим вызовы функций из стандартной библиотеки ввода-вывода на обычные read и write. Это даст возможность отказаться от функций стандартной библиотеки ввода-вывода с неблокируемыми дескрипторами, так как их применение может привести к катастрофическим последствиям.

Мы работаем с двумя буферами: буфер to содержит данные, направляющиеся из стандартного потока ввода к серверу, а буфер fr — данные, приходящие от сервера в стандартный поток вывода. На рис. 16.1 представлена организация буфера to и указателей в буфере.

Рис. 16.1. Буфер, содержащий данные из стандартного потока ввода, идущие к сокету

Указатель toiptr указывает на следующий байт, в который данные могут быть считаны из стандартного потока ввода. Указатель tooptr указывает на следующий байт, который должен быть записан в сокет. Число байтов, которое может быть считано из стандартного потока ввода, равно &to[MAXLINE] минус toiptr. Как только значение tooptr достигает toiptr, оба указателя переустанавливаются на начало буфера.

На рис. 16.2 показана соответствующая организация буфера fr. В листинге 16.1[1] представлена первая часть функции.

Рис. 16.2. Буфер, содержащий данные из сокета, идущие к стандартному устройству вывода

Листинг 16.1. Функция str_cli: первая часть, инициализация и вызов функции

//nonblock/strclinonb.c

 1 #include "unp.h"


 2 void

 3 str_cli(FILE *fp, int sockfd)

 4 {

 5  int maxfdp1, val, stdineof;

 6  ssize_t n, nwritten;

 7  fd_set rset, wset;

 8  char to[MAXLINE], fr[MAXLINE];

 9  char *toiptr, *tooptr, *friptr, *froptr;


10  val = Fcntl(sockfd, F_GETFL, 0);

11  Fcntl(sockfd, F_SETFL, val | O_NONBLOCK);


12  val = Fcntl(STDIN_FILENO, F_SETFL, 0);

13  Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK);


14  val = Fcntl(STDOUT_FILENO, F_SETFL, 0);

15  Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);


16  toiptr = tooptr = to; /* инициализация указателей буфера */

17  friptr = froptr = fr;

18  stdineof = 0;


19  maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;

20  for (;;) {

21   FD_ZERO(&rset);

22   FD_ZERO(&wset);

23   if (stdineof == 0 && toiptr < &to[MAXLINE])

24     FD_SET(STDIN_FILENO, &rset); /* чтение из стандартного потока

                                       ввода */

25   if (friptr < &fr[MAXLINE])

26    FD_SET(sockfd, &rset); /* чтение из сокета */

27   if (tooptr != toiptr)

28    FD_SET(sockfd, &wset); /* данные для записи в сокет */

29   if (froptr != friptr)

30    FD_SET(STDOUT_FILENO, &wset); /* данные для записи в стандартный

                                       поток вывода */

31   Select(maxfdp1, &rset, &wset, NULL, NULL);

Установка неблокируемых дескрипторов

10-15 Все три дескриптора делаются неблокируемыми при помощи функции fcntl: сокет в направлении к серверу и от сервера, стандартный поток ввода и стандартный поток вывода.

Инициализация указателей буфера

16-19 Инициализируются указатели в двух буферах и вычисляется максимальный дескриптор. Это значение, увеличенное на единицу, будет использоваться в качестве первого аргумента функции select.

Основной цикл: подготовка к вызову функции select

20 Как и в случае первой версии этой функции, показанной в листинге 6.2, основной цикл функции содержит вызов функции select, за которой следуют отдельные проверки различных интересующих нас условий.

Подготовка интересующих нас дескрипторов

21-30 Оба набора дескрипторов обнуляются и затем в каждом наборе включается не более двух битов. Если мы еще не прочитали конец файла из стандартного потока ввода и есть место как минимум для 1 байта данных в буфере to, то в наборе флагов чтения включается бит, соответствующий стандартному потоку ввода. Если есть место как минимум для 1 байта данных в буфере fr, то в наборе флагов чтения включается бит, соответствующий сокету. Если есть данные для записи в сокет в буфере to, то в наборе флагов записи включается бит, соответствующий сокету. Наконец если в буфере fr есть данные для отправки в стандартный поток вывода, то в наборе флагов записи включается бит, соответствующий этому стандартному потоку.

Вызов функции select

31 Вызывается функция select, ожидающая, когда одно из четырех условий станет истинным. Для этой функции мы не задаем тайм-аута.

Следующая часть нашей функции показана в листинге 16.2. Этот код содержит первые две проверки (из четырех возможных), выполняемые после завершения функции select.

Листинг 16.2. Функция str_cli: вторая часть, чтение из стандартного потока ввода или сокета

//nonblock/strclinonb.c

32   if (FD_ISSET(STDIN_FILENO, &rset)) {

33    if ((n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0) {

34     if (errno != EWOULDBLOCK)

35      err_sys("read error on stdin");

36    } else if (n == 0) {

37     fprintf(stderr, "%s: EOF on stdinn", gf_time());

38     stdineof = 1; /* с stdin все сделано */

39     if (tooptr == toiptr)

40      Shutdown(sockfd, SHUT_WR); /* отсылаем FIN */


41    } else {

42     fprintf(stderr, "%s: read %d bytes from stdinn", gf_time(),

43      n);

44     toiptr += n; /* только что полученное из функции read число */

45     FD_SET(sockfd, &wset); /* включаем бит в наборе чтения */

46    }

47   }

48   if (FD_ISSET(sockfd, &rset)) {

49    if ((n = read(sockfd, friptr, &fr[MAXLINE] - friptr)) < 0) {

50     if (errno != EWOULDBLOCK)

51      err_sys("read error on socket");


52    } else if (n == 0) {

53     fprintf(stderr, "%s: EOF on socketn", gf_time());

54     if (stdineof)

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

56     else

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


58    } else {

59     fprintf(stderr, "%s: read %d bytes from socketn",

60      gf_time(), n);

61     friptr += n; /* только что полученное из функции read число */

62     FD_SЕТ(STDOUT_FILЕNO, &wset); /* включаем бит в наборе

                                        чтения */

63    }

64   }

Чтение из стандартного потока ввода с помощью функции read

32-33 Если стандартный поток ввода готов для чтения, мы вызываем функцию read. Третий ее аргумент — это количество свободного места в буфере to.

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