Читаем UNIX: разработка сетевых приложений полностью

Мы всегда начинаем поиск свободного дочернего процесса с первого элемента массива структур Child. Это означает, что новое соединение для обработки поступившего клиентского запроса всегда получает первый элемент этого массива. Этот факт мы проверим при рассмотрении табл. 30.2 и значения счетчика child_count после завершения работы сервера. Если мы не хотим оказывать такое предпочтение первому элементу массива, мы можем запомнить, какой дочерний процесс получил последнее клиентское соединение, и каждый раз начинать поиск свободного дочернего процесса со следующего за ним, а по достижении конца массива переходить снова к первому элементу. В этом нет особого смысла (на самом деле все равно, какой дочерний процесс обрабатывает очередное соединение, если имеется несколько свободных дочерних процессов), если только планировочный алгоритм операционной системы не накладывает санкций на процессы, которые требуют относительно больших временных затрат центрального процессора. Более равномерное распределение загрузки между всеми дочерними процессами приведет к выравниванию времен, затраченных на их выполнение.

Обработка вновь освободившихся дочерних процессов

56-66 Когда дочерний процесс заканчивает обработку клиентского запроса, наша функция child_main записывает один байт в канал для родительского процесса. Тем самым родительский конец канала становится доступным для чтения. Упомянутый байт считывается (но его значение при этом игнорируется), а дочерний процесс помечается как свободный. Если же дочерний процесс завершит свое выполнение неожиданно, его конец канала будет закрыт, а операция чтения (read) возвратит нулевое значение. Это значение перехватывается и дочерний процесс завершается, но более удачным решением было бы записать ошибку и создать новый дочерний процесс для замены завершенного.

Функция child_main показана в листинге 30.19.

Листинг 30.19. Функция child_main: передача дескриптора в сервере с предварительным порождением дочерних процессов

//server/child05.c

23 void

24 child_main(int i, int listenfd, int addrlen)

25 {

26  char c;

27  int connfd;

28  ssize_t n;

29  void web_child(int);

30  printf("child %ld starting\n", (long)getpid());

31  for (;;) {

32   if ((n = Read_fd(STDERR_FILENO, &c, 1, &connfd)) == 0)

33    err_quit("read_fd returned 0");

34   if (connfd < 0)

35    err_quit("no descriptor from read_fd");

36   web_child(connfd); /* обработка запроса */

37   Close(connfd);

38   Write(STDERR_FILENO, "", 1); /* сообщаем родительскому процессу

                                     о том, что дочерний освободился */

39  }

40 }

Ожидание дескриптора от родительского процесса

32-33 Эта функция отличается от аналогичных функций из двух предыдущих разделов, так как дочерний процесс не вызывает более функцию accept. Вместо этого дочерний процесс блокируется в вызове функции read_fd, ожидая, когда родительский процесс передаст ему дескриптор присоединенного сокета.

Сообщение родительскому процессу о готовности дочернего к приему новых запросов

38 Закончив обработку очередного клиентского запроса, мы записываем (write) 1 байт в канал, чтобы сообщить, что данный дочерний процесс освободился.

В табл. 30.1 при сравнении строк 4 и 5 мы видим, что данный сервер медленнее, чем версия, рассмотренная нами в предыдущем разделе, которая использовала блокировку потоками взаимного исключения. Передача дескриптора по каналу от родительского процесса к дочернему и запись одного байта в канал для сообщения родительскому процессу о завершении обработки клиентского запроса занимает больше времени, чем блокирование и разблокирование взаимного исключения или файла.

В табл. 30.2 показаны значения счетчиков child_count из структуры Child, которые выводятся обработчиком сигнала SIGINT по завершении работы сервера. Дочерние процессы, расположенные ближе к началу массива, обрабатывают большее количество клиентских запросов, как было указано при обсуждении листинга 30.18.

<p>30.10. Параллельный сервер TCP: один поток для каждого клиента</p>

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

Перейти на страницу:

Все книги серии Мастер-класс

Секреты резьбы по дереву
Секреты резьбы по дереву

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

Галина Алексеевна Серикова

Сделай сам / Хобби и ремесла / Руководства / Дом и досуг / Словари и Энциклопедии

Похожие книги