Традиционно параллельный сервер TCP вызывает функцию fork
для порождения нового дочернего процесса, который будет выполнять обработку очередного клиентского запроса. Это позволяет серверу обрабатывать несколько запросов одновременно, выделяя по одному дочернему процессу для каждого клиента. Единственным ограничением на количество одновременно обрабатываемых клиентских запросов является ограничение операционной системы на количество дочерних процессов, допустимое для пользователя, в сеансе которого работает сервер. Листинг 5.9 содержит пример параллельного сервера, и большинство серверов TCP написаны в том же стиле.
Проблема с параллельными серверами заключается в количестве времени, которое тратит центральный процессор на выполнение функции fork
для порождения нового дочернего процесса для каждого клиента. Давным-давно, в конце 80-х годов XX века, когда наиболее загруженные серверы обрабатывали сотни или тысячи клиентов за день, это было приемлемо. Но расширение Сети изменило требования. Теперь загруженными считаются серверы, обрабатывающие миллионы соединений TCP в день. Сказанное относится лишь к одиночным узлам, но наиболее загруженные сайты используют несколько узлов, распределяя нагрузку между ними (в разделе 14.2 [112] рассказывается об общепринятом способе распределения этой нагрузки, называемом fork
для каждого клиентского запроса, но тем не менее параллельные серверы остаются широко распространенными.
В листинге 30.2 показана функция main
для нашего параллельного сервера TCP.
Листинг 30.2. Функция main для параллельного сервера TCP
//server/serv01.c
1 include "unp.h"
2 int
3 main(int argc, char **argv)
4 {
5 int listenfd, connfd;
6 pid_t childpid;
7 void sig_chld(int), sig_int(int), web_child(int);
8 socklen_t clilen, addrlen;
9 struct sockaddr *cliaddr;
10 if (argc == 2)
11 listenfd = Tcp_listen(NULL, argv[1], &addrlen);
12 else if (argc == 3)
13 listenfd = Tcp_listen(argv[1], argv[2], &addrlen);
14 else
15 err_quit("usage: serv01 [
16 cliaddr = Malloc(addrlen);
17 Signal(SIGCHLD, sig_chld);
18 Signal(SIGINT, sig_int);
19 for (;;) {
20 clilen = addrlen;
21 if ((connfd = accept(listenfd, cliaddr, &clilen)) < 0) {
22 if (errno == EINTR)
23 continue; /* назад к for() */
24 else
25 err_sys("accept error");
26 }
27 if ((childpid = Fork()) == 0) { /* дочерний процесс */
28 Close(listenfd); /* закрываем прослушиваемый сокет */
29 web_child(connfd); /* обрабатываем запрос */
30 exit(0);
31 }
32 Close(connfd); /* родительский процесс закрывает
присоединенный сокет */
33 }
34 }
Эта функция аналогична функции, показанной в листинге 5.9: она вызывает функцию fork
для каждого клиентского соединения и обрабатывает сигналы SIGCHLD
, приходящие от закончивших свое выполнение дочерних процессов. Тем не менее мы сделали эту функцию не зависящей от протокола за счет вызова функции tcp_listen
. Мы не показываем обработчик сигнала sig_chld
: он совпадает с показанным в листинге 5.8, но только без функции printf
.
Мы также перехватываем сигнал SIGINT
, который генерируется при вводе символа прерывания. Мы вводим этот символ после завершения работы клиента, чтобы было выведено время, потраченное центральным процессором на выполнение данной программы. В листинге 30.3 показан обработчик сигнала. Это пример обработчика сигнала, который никогда не возвращает управление.
Листинг 30.3. Обработчик сигнала SIGINT
//server/serv01.c
35 void
36 sig_int(int signo)
37 {
38 void pr_cpu_time(void);
39 pr_cpu_time();
40 exit(0);
41 }
В листинге 30.4 показана функция pr_cpu_time
, вызываемая из обработчика сигнала.
Листинг 30.4. Функция pr_cpu_time: вывод полного времени центрального процессора
Вильям Л Саймон , Вильям Саймон , Наталья Владимировна Макеева , Нора Робертс , Юрий Викторович Щербатых
Зарубежная компьютерная, околокомпьютерная литература / ОС и Сети, интернет / Короткие любовные романы / Психология / Прочая справочная литература / Образование и наука / Книги по IT / Словари и Энциклопедии