UNIX: разработка сетевых приложений
Часть 347 из 399 Информация о книге
16 err_quit("usage: serv02 [ <host> ] <port#> <#children>");17 nchildren = atoi(argv[argc - 1]);18 pids = Calloc(nchildren, sizeof(pid_t));19 for (i = 0; i < nchildren; i++)20 pids[i] = child_make(i, listenfd, addrlen); /* возвращение родительского процесса */21 Signal (SIGINT, sig_int);22 for (;;)23 pause(); /* дочерние процессы завершились */24 }Дополнительный аргумент командной строки указывает, сколько требуется создать дочерних процессов. В памяти выделяется место для размещения массива, в который записываются идентификаторы дочерних процессов, используемые функцией11-18при окончании работы программы для завершения этих процессов.mainКаждый дочерний процесс создается функцией19-20, которую мы показываем в листинге 30.8.child_makeКод обработчика сигнала
, представленный в листинге 30.7, отличается от кода, приведенного в листинге 30.3.SIGINTЛистинг 30.7. Обработчик сигнала SIGINT
//server/serv02.c25 void26 sig_int(int signo)27 {28 int i;29 void pr_cpu_time(void);30 /* завершаем все дочерние процессы */31 for (i = 0; i < nchildren; i++)32 kill(pids[i], SIGTERM);33 while (wait(NULL) > 0) /* ждем завершения всех дочерних процессов */34 ;35 if (errno != ECHILD)36 err_sys("wait error");37 pr_cpu_time();38 exit(0);39 }Функция30-34сообщает об использовании ресурсов всеми дочерними процессами, завершившими свое выполнение, поэтому мы должны завершить все дочерние процессы к моменту вызова функцииgetrusage. Для этого дочерним процессам посылается сигналpr_cpu_time, после чего мы вызываем функциюSIGTERMи ждем завершения выполнения дочерних процессов.waitВ листинге 30.8 показана функция
, вызываемая из функции main для порождения очередного дочернего процесса.child_makeЛистинг 30.8. Функция child_make: создание очередного дочернего процесса
//server/child02.c1 #include "unp.h"2 pid_t3 child_make(int i, int listenfd, int addrlen)4 {5 pid_t pid;6 void child_main(int, int, int);7 if ( (pid = Fork()) > 0)8 return (pid); /* родительский процесс */9 child_main(i, listenfd, addrlen); /* никогда не завершается */10 }Функция7-9создает очередной дочерний процесс и возвращает родителю идентификатор дочернего процесса. Дочерний процесс вызывает функциюfork, показанную в листинге 30.9, которая представляет собой бесконечный цикл.child_mainЛистинг 30.9. Функция child_main: бесконечный цикл, выполняемый каждым дочерним процессом
//server/child02.c11 void12 child_main(int i, int listenfd, int addrlen)13 {14 int connfd;15 void web_child(int);16 socklen_t clilen;17 struct sockaddr *cliaddr;18 cliaddr = Malloc(addrlen);19 printf("child %ld starting\n", (long)getpid());20 for (;;) {21 clilen = addrlen;22 connfd = Accept(listenfd, cliaddr, &clilen);23 web_child(connfd); /* обработка запроса */24 Close(connfd);25 }26 }Каждый дочерний процесс вызывает функцию20-25, и когда она завершается, функцияaccept(см. листинг 30.5) обрабатывает клиентский запрос. Дочерний процесс продолжает выполнение цикла, пока родительский процесс не завершит его.web_childРеализация 4.4BSD
Если вы никогда ранее не сталкивались с таким типом устройства сервера (несколько процессов, вызывающих функцию
на одном и том же прослушиваемом сокете), вас, вероятно, удивляет, что это вообще может работать. Пожалуй, здесь уместен краткий экскурс, описывающий реализацию этого механизма в Беркли-ядрах (более подробную информацию вы найдете в [128]).acceptРодитель сначала создает прослушиваемый сокет, а затем — дочерние процессы. Напомним, что каждый раз при вызове функции
происходит копирование всех дескрипторов в каждый дочерний процесс. На рис. 30.2 показана организация структурfork(по одной структуре на процесс), одна структураprocдля прослушиваемого дескриптора и одна структураfile.socketРис. 30.2. Организация структур proc, file и socket
