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

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

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

63    }

64   }

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

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

Обработка ошибки

34-35 Если происходит ошибка EWOULDBLOCK, мы ничего не предпринимаем. Обычно эта ситуация — когда функция select сообщает нам о том, что дескриптор готов для чтения, а функция read возвращает ошибку EWOULDBLOCK — не должна возникать, но тем не менее мы ее обрабатываем.

Возвращение конца файла функцией read

36-40 Если функция read возвращает нуль, мы закончили со стандартным потоком ввода. Флаг stdineof установлен. Если в буфере to больше нет данных для отправки (tooptr равно toiptr), функция shutdown отправляет серверу сегмент FIN. Если в буфере to еще есть данные для отправки, сегмент FIN не может быть отправлен до тех пор, пока содержимое буфера не будет записано в сокет.

ПРИМЕЧАНИЕ

Мы выводим в стандартный поток сообщений об ошибках строку, отмечающую конец файла, вместе с текущим временем. Мы покажем, как мы используем этот вывод, после описания функции. Аналогичные вызовы функции fprintf выполняются неоднократно в процессе выполнения нашей функции.

Возвращение данных функцией read

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

ПРИМЕЧАНИЕ

Это одно из непростых конструктивных решений, которые приходится принимать при написании кода. У нас есть несколько альтернатив. Вместо установки бита в наборе записи мы можем ничего не делать, и в этом случае функция select будет проверять возможность записи в сокет, когда она будет вызвана в следующий раз. Но это требует дополнительного прохода цикла и вызова функции select, когда мы уже знаем, что у нас есть данные для записи в сокет. Другой вариант — дублировать код, который записывает в сокет, но это кажется расточительным, к тому же это возможный источник ошибки (в случае, если в этой части дублируемого кода есть ошибка и мы обнаруживаем и устраняем ее только в одном месте). Наконец, мы можем создать функцию, записывающую в сокет, и вызывать эту функцию вместо дублирования кода, но эта функция должна использовать три локальные переменные совместно с функцией str_cli, что может привести к необходимости сделать эти переменные глобальными. Выбор, сделанный в нашем случае, — это результат субъективного мнения автора относительно того, какой из описанных трех вариантов предпочтительнее.

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

48-64 Эти строки кода аналогичны выражению if, только что описанному для случая, когда стандартный поток ввода готов для чтения. Если функция read возвращает ошибку EWOULDBLOCK, ничего не происходит. Если мы встречаем признак конца файла, присланный сервером, это нормально, когда мы уже получили признак конца файла в стандартном потоке ввода. Но иначе это будет ошибкой, означающей преждевременное завершение работы сервера (Server terminated prematurely). Если функция read возвращает некоторые данные, friptr увеличивается на единицу и в наборе флагов записи включается бит для стандартного потока вывода, с тем чтобы попытаться записать туда данные в следующей части функции.

В листинге 16.3 показана последняя часть нашей функции.

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

//nonblock/strclinonb.c

65   if (FD_ISSET(STDOUT_FILENO, &wset) && ((n = friptr - froptr) > 0)) {

66    if ((nwritten = write(STDOUT_FILENO, froptr, n)) < 0) {

67     if (errno != EWOULDBLOCK)

68      err_sys("write error to stdout");

69    } else {

70     fprintf(stderr, "%s: wrote %d bytes to stdoutn",

71      gf_time(), nwritten);

72     froptr += nwritten; /* только что полученное из функции write

                              число */

73     if (froptr == friptr)

74      froptr = friptr - fr; /* назад к началу буфера */

75    }

76   }


77   if (FD_ISSET(sockfd, &wset) && ((n - toiptr - tooptr) > 0)) {

78    if ((nwritten = write(sockfd, tooptr, n)) < 0) {

79     if (errno != EWOULDBLOCK)

80      err_sys("write error to socket");


81    } else {

82     fprintf(stderr, "%s: wrote %d bytes to socketn",

83      gf_time(), nwritten);

84     tooptr += nwritten; /* только что полученное из функции write

                              число */

85     if (tooptr == toiptr) {

86      toiptr - tooptr = to; /* назад к началу буфера */

87      if (stdineof)

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

89     }

90    }

91   }

92  }

93 }

Запись в стандартный поток вывода с помощью функции write

65-68 Если есть возможность записи в стандартный поток вывода и число байтов для записи больше нуля, вызывается функция write. Если возвращается ошибка EWOULDBLOCK, ничего не происходит. Обратите внимание, что это условие возможно, поскольку код в конце предыдущей части функции включает бит в наборе флагов записи для стандартного потока вывода, когда не известно, успешно выполнилась функция write или нет.

Успешное выполнение функции write

68-74 Если функция write выполняется успешно, froptr увеличивается на число записанных байтов. Если указатель вывода стал равен указателю ввода, оба указателя переустанавливаются на начало буфера.

Запись в сокет с помощью функции write

76-90 Эта часть кода аналогична коду, только что описанному для записи в стандартный поток вывода. Единственное отличие состоит в том, что когда указатель вывода доходит до указателя ввода, не только оба указателя переустанавливаются в начало буфера, но и появляется возможность отправить серверу сегмент FIN.

Теперь мы проверим работу этой функции и операций неблокируемого ввода-вывода. В листинге 16.4 показана наша функция gf_time, вызываемая из функции str_cli.

Листинг 16.4. Функция gf_time: возвращение указателя на строку времени

//lib/gf_time.c

 1 #include "unp.h"

 2 #include <time.h>


 3 char*

 4 gf_time(void)

 5 {

 6  struct timeval tv;

 7  static char str[30];

 8  char *ptr;


 9  if (gettimeofday(&tv, NULL) < 0)

10   err_sys("gettimeofday error");


11  ptr = ctime(&tv.tv_sec);

12  strcpy(str, &ptr[11]);

13  /* Fri Sep 13 00:00:00 1986n */

14  /* 0123456789012345678901234 5 */

15  snprintf(str + 8, sizeof(str) - 8, ".%06ld", tv.tv_usec);

15  return (str);

17 }

Эта функция возвращает строку, содержащую текущее время с точностью до микросекунд, в таком формате:

12:34:56.123456

Здесь специально используется тот же формат, что и для отметок времени, которые выводятся программой tcpdump. Обратите внимание, что все вызовы функции fprintf в нашей функции str_cli записывают данные в стандартный поток сообщений об ошибках, позволяя нам отделить данные стандартного потока вывода (строки, отраженные сервером) от наших диагностических данных. Затем мы можем запустить наш клиент и функцию tcpdump, получить эти диагностические данные вместе с результатом функции tcpdump и отсортировать вместе два вида выходных данных в порядке их получения. Это позволит нам увидеть, что происходит в нашей программе, и соотнести это с действиями TCP.

Например, сначала мы запускаем функцию tcpdump на нашем узле solaris, собирая только сегменты TCP, идущие к порту 7 или от него (эхо-сервер), и сохраняем выходные данные в файле, который называется tcpd:

solaris % tcpdump -w tcpd tcp and port 7

Затем мы запускаем клиент TCP на этом узле и указываем сервер на узле linux:

solaris % tcpcli02 192.168.1.10 < 2000.lines > out 2> diag

Стандартный поток ввода — это файл 2000.lines, тот же файл, что мы использовали для листинга 6.2. Стандартный поток вывода перенаправляется в файл out, а стандартный поток сообщений об ошибках — в файл diag. По завершении мы запускаем:

solaris % diff 2000.lines out

чтобы убедиться, что отраженные строки идентичны введенным строкам. Наконец, мы прекращаем выполнение функции tcpdump нажатием соответствующей клавиши терминала, после чего выводим записи функции tcpdump, сортируя их по времени получения вместе с данными диагностики, полученными от клиента. В листинге 16.5 показана первая часть этого результата.

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