Более простая версия функции str_cli

Более простая версия функции str_cli

Неблокируемая версия функции str_cli, которую мы только что показали, нетривиальна: около 135 строк кода по сравнению с 40 строками версии, использующей функцию select с блокируемым вводом-выводом (см. листинг 6.2), и 20 строками начальной версии, работающей в режиме остановки и ожидания (см. листинг 5.4). Мы знаем, что эффект от удлинения кода в два раза, с 20 до 40 строк оправдывает затраченные усилия, поскольку в пакетном режиме скорость возрастает почти в 30 раз, а применение функции select с блокируемыми дескрипторами осуществляется не слишком сложно. Но будут ли оправданы затраченные усилия при написании приложения, использующего неблокируемый ввод-вывод, с учетом усложнения итогового кода? Нет, ответим мы. Если нам необходимо использовать неблокируемый ввод-вывод, обычно бывает проще разделить приложение либо на процессы (при помощи функции fork), либо на потоки (см. главу 26).

В листинге 16.6 показана еще одна версия нашей функции str_cli, разделяемая на два процесса при помощи функции fork.

Эта функция сразу же вызывает функцию fork для разделения на родительский и дочерний процессы. Дочерний процесс копирует строки от сервера в стандартный поток вывода, а родительский процесс — из стандартного потока ввода серверу, как показано на рис. 16.4.

Рис. 16.4. Функция str_cli, использующая два процесса

Мы показываем, что соединения TCP являются двусторонними и что родительский и дочерний процессы совместно используют один и тот же дескриптор сокета: родительский процесс записывает в сокет, а дочерний процесс читает из сокета. Есть только один сокет, один буфер приема сокета и один буфер отправки, но на этот сокет ссылаются два дескриптора: один в родительском процессе и один в дочернем.

Листинг 16.6. Версия функции str_cli, использующая функцию fork

//nonblock/strclifork.c

 1 #include "unp.h"

 2 void

 3 str_cli(FILE *fp, int sockfd)

 4 {

 5  pid_t pid;

 6  char sendline[MAXLINE], recvline[MAXLINE];

 7  if ((pid = Fork()) == 0) { /* дочерний процесс: сервер -> stdout */

 8   while (Readline(sockfd, recvline, MAXLINE) > 0)

 9    Fputs(recvline, stdout);

10   kill(getppid(), SIGTERM); /* в случае, если родительский процесс

                                 все еще выполняется */

11   exit(0);

12  }

13  /* родитель: stdin -> сервер */

14  while (Fgets(sendline, MAXLINE, fp) != NULL)

15   Writen(sockfd, sendline, strlen(sendline));

16  Shutdown(sockfd, SHUT_WR); /* конец файла на stdin, посылаем FIN */

17  pause();

18  return;

19 }

Нам нужно снова вспомнить о последовательности завершения соединения. Обычное завершение происходит, когда в стандартном потоке ввода встречается конец файла. Родительский процесс считывает конец файла и вызывает функцию shutdown для отправки сегмента FIN. (Родительский процесс не может вызвать функцию close, см. упражнение 16.1.) Но когда это происходит, дочерний процесс должен продолжать копировать от сервера в стандартный поток вывода, пока он не получит признак конца файла на сокете.

Также возможно, что процесс сервера завершится преждевременно (см. раздел 5.12), и если это происходит, дочерний процесс считывает признак конца файла на сокете. В таком случае дочерний процесс должен сообщить родительскому, что нужно прекратить копирование из стандартного потока ввода в сокет (см. упражнение 16.2). В листинге 16.6 дочерний процесс отправляет родительскому процессу сигнал SIGTERM, в случае, если родительский процесс еще выполняется (см. упражнение 16.3). Другим способом обработки этой ситуации было бы завершение дочернего процесса, и если родительский процесс все еще выполнялся бы к этому моменту, он получил бы сигнал SIGCHLD.

Родительский процесс вызывает функцию pause, когда заканчивает копирование, что переводит его в состояние ожидания того момента, когда будет получен сигнал. Даже если родительский процесс не перехватывает никаких сигналов, он все равно переходит в состояние ожидания до получения сигнала SIGTERM от дочернего процесса. По умолчанию действие этого сигнала — завершение процесса, что вполне устраивает нас в этом примере. Родительский процесс ждет завершения дочернего процесса, чтобы измерить точное время для этой версии функции str_cli. Обычно дочерний процесс завершается после родительского, но поскольку мы измеряем время, используя команду оболочки time, измерение заканчивается, когда завершается родительский процесс.

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

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

Шаг первый: простая страница

Из книги Разгони свой сайт автора Мациевский Николай

Шаг первый: простая страница Вначале бралась обычная страница, для которой использовалось только gzip-сжатие HTML-файла. Это самое простое, что может быть сделано для ускорения загрузки страницы. Данная оптимизация бралась за основу, с которой сравнивалось все остальное. Для


Глава 4 Простая

Из книги 3ds Max 2008 для дизайна интерьеров автора Семак Рита

Глава 4 Простая В этой главе рассматриваются модификаторы и составные объекты. Действие модификаторов направлено на изменение формы объекта, взаимодействие двух объектов приводит к созданию третьего – составного. Моделирование с использованием модификаторов и


Простая архитектура PKI

Из книги Инфраструктуры открытых ключей автора Полянская Ольга Юрьевна

Простая архитектура PKI Архитектура PKI может быть очень простой, если у пользователей - простые требования. В этом разделе рассматриваются два типа простой архитектуры: одиночный УЦ и списки доверия УЦ. Простая архитектура достаточна для связывания пользователей одного и


17.1.1. Простая разметка

Из книги Программирование на языке Ruby [Идеология языка, теория и практика применения] автора Фултон Хэл


Первая простая реализация

Из книги Фундаментальные алгоритмы и структуры данных в Delphi автора Бакнелл Джулиан М.

Первая простая реализация При разработке очереди по приоритету первый атрибут (возможность хранения произвольного количества элементов) наталкивает на мысль об использовании какой-либо расширяемой структуры данных типа связного списка или расширяемого массива,


Вторая простая реализация

Из книги Искусство программирования на языке сценариев командной оболочки автора Купер Мендель

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


Пример 22-1. Простая функция

Из книги Программирование для Linux. Профессиональный подход автора Митчелл Марк

Пример 22-1. Простая функция #!/bin/bashfunky (){ echo "Это обычная функция."} # Функция должна быть объявлена раньше, чем ее можно будет использовать. # Вызов функции.funkyexit 0Функция должна быть объявлена раньше, чем ее можно будет использовать. К сожалению, в Bash нет возможности


9.2. Простая ассемблерная вставка

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

9.2. Простая ассемблерная вставка Вот как с помощью функции asm() осуществляется сдвиг числа на 8 битов вправо:asm("shrl $8, %0" : "=r" (answer) : "r" (operand) : "cc");Выражение в скобках состоит из секций, разделенных двоеточиями. В первой секции указана ассемблерная инструкция и ее операнды.


5.5. Эхо-клиент TCP: функция str_cli

Из книги автора

5.5. Эхо-клиент TCP: функция str_cli Эта функция, показанная в листинге 5.4, обеспечивает отправку запроса клиента и прием ответа сервера в цикле. Функция считывает строку текста из стандартного потока ввода, отправляет ее серверу и считывает отраженный ответ сервера, после чего


6.4. Функция str_cli (продолжение)

Из книги автора

6.4. Функция str_cli (продолжение) Теперь мы можем переписать нашу функцию str_cli, представленную в разделе 5.5 (на этот раз используя функцию select), таким образом, чтобы мы получали уведомление, как только завершится процесс сервера. Проблема с предыдущей версией состояла в том,


6.7. Функция str_cli (еще раз)

Из книги автора

6.7. Функция str_cli (еще раз) В листинге 6.2 представлена наша обновленная (и корректная) функция str_cli. В этой версии используются функции select и shutdown. Первая уведомляет нас о том, когда сервер закрывает свой конец соединения, а вторая позволяет корректно обрабатывать пакетный


Сравнение времени выполнения различных версий функции str_cli

Из книги автора

Сравнение времени выполнения различных версий функции str_cli Итак, мы продемонстрировали четыре различных версии функции str_cli. Для каждой версии мы покажем время, которое потребовалось для ее выполнения, в том числе и для версии, использующей программные потоки (см.


26.3. Использование потоков в функции str_cli

Из книги автора

26.3. Использование потоков в функции str_cli В качестве первого примера использования потоков мы перепишем нашу функцию str_cli. В листинге 16.6 была представлена версия этой функции, в которой использовалась функция fork. Напомним, что были также представлены и некоторые другие