30.5. Параллельный сервер TCP: один дочерний процесс для каждого клиента
30.5. Параллельный сервер TCP: один дочерний процесс для каждого клиента
Традиционно параллельный сервер TCP вызывает функцию fork для порождения нового дочернего процесса, который будет выполнять обработку очередного клиентского запроса. Это позволяет серверу обрабатывать несколько запросов одновременно, выделяя по одному дочернему процессу для каждого клиента. Единственным ограничением на количество одновременно обрабатываемых клиентских запросов является ограничение операционной системы на количество дочерних процессов, допустимое для пользователя, в сеансе которого работает сервер. Листинг 5.9 содержит пример параллельного сервера, и большинство серверов TCP написаны в том же стиле.
Проблема с параллельными серверами заключается в количестве времени, которое тратит центральный процессор на выполнение функции fork для порождения нового дочернего процесса для каждого клиента. Давным-давно, в конце 80-х годов XX века, когда наиболее загруженные серверы обрабатывали сотни или тысячи клиентов за день, это было приемлемо. Но расширение Сети изменило требования. Теперь загруженными считаются серверы, обрабатывающие миллионы соединений TCP в день. Сказанное относится лишь к одиночным узлам, но наиболее загруженные сайты используют несколько узлов, распределяя нагрузку между ними (в разделе 14.2 [112] рассказывается об общепринятом способе распределения этой нагрузки, называемом циклическим обслуживанием DNS — DNS round robin). В последующих разделах описаны различные способы, позволяющие избежать вызова функции 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 [ <host> ] <port#>");
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: вывод полного времени центрального процессора
//server/pr_cpu_time.c
1 #include "unp.h"
2 #include <sys/resource.h>
3 #ifndef HAVE_GETRUSAGE_PROTO
4 int getrusage(int, struct rusage*);
5 #endif
6 void
7 pr_cpu_time(void)
8 {
9 double user, sys;
10 struct rusage myusage, childusage;
11 if (getrusage(RUSAGE_SELF, &myusage) < 0)
12 err_sys("getrusage error");
13 if (getrusage(RUSAGE_CHILDREN, &childusage) < 0)
14 err_sys("getrusage error");
15 user = (double)myusage.ru_utime.tv_sec +
16 myusage.ru_utime.tv_usec / 1000000.0;
17 user += (double)childusage.ru_utime.tv_sec +
18 childusage.ru_utime.tv_usec / 1000000.0;
19 sys = (double)myusage.ru_stime.tv_sec +
20 myusage.ru_stime.tv_usec / 1000000.0;
21 sys += (double)childusage.ru_stime.tv_sec +
22 childusage.ru_stime.tv_usec / 1000000.0;
21 printf(" user time = %g, sys time = %g ", user, sys);
22 }
Функция getrusage вызывается дважды: она позволяет получить данные об использовании ресурсов вызывающим процессом (RUSAGE_SELF) и всеми его дочерними процессами, которые завершили свое выполнение (RUSAGE_CHILDREN). Выводится время, затраченное центральным процессором на выполнение пользовательского процесса (общее пользовательское время, total user time), и время, которое центральный процессор затратил внутри ядра на выполнение задач, заданных вызывающим процессом (общее системное время, total system time).
Возвращаясь к листингу 30.2, мы видим, что для обработки каждого клиентского запроса вызывается функция web_child. Эта функция показана в листинге 30.5.
Листинг 30.5. Функция web_child: обработка каждого клиентского запроса
//server/web_child.c
1 #include "unp.h"
2 #define MAXN 16384 /* максимальное количество байтов, которое клиент
может запросить */
3 void
4 web_child(int sockfd)
5 {
6 int ntowrite;
7 ssize_t nread;
8 char line[MAXLINE], result[MAXN];
9 for (;;) {
10 if ((nread = Readline(sockfd, line, MAXLINE)) == 0)
11 return; /* соединение закрыто другим концом */
12 /* line задает, сколько байтов следует отправлять обратно */
13 ntowrite = atol(line);
14 if ((ntowrite <= 0) || (ntowrite > MAXN))
15 err_quit("client request for bytes", ntowrite);
16 Writen(sockfd, result, ntowrite);
17 }
18 }
Установив соединение с сервером, клиент записывает одну строку, задающую количество байтов, которое сервер должен вернуть. Это отчасти похоже на HTTP: клиент отправляет небольшой запрос, а сервер в ответ отправляет требуемую информацию (часто это файл HTML или изображение GIF). В случае HTTP сервер обычно закрывает соединение после отправки клиенту затребованных данных, хотя более новые версии используют постоянные соединения (persistent connection), оставляя соединения TCP открытыми для дополнительных клиентских запросов. В нашей функции web_child сервер допускает дополнительные запросы от клиента, но, как мы видели в листинге 24.1, клиент посылает серверу только по одному запросу на каждое соединение, а по получении ответа от сервера это соединение закрывается.
В строке 1 табл. 30.1 показаны результаты измерения времени, затраченного параллельным сервером. При сравнении со следующими строками этой таблицы видно, что параллельный сервер тратит больше процессорного времени, чем все другие типы серверов — то, что мы и ожидали при вызове функции fork.
ПРИМЕЧАНИЕ
Один из способов устройства сервера, который мы не рассматриваем в этой главе, — это сервер, инициируемый демоном inetd (см. раздел 13.5). С точки зрения управления процессами такой сервер подразумевает использование функций fork и exec, так что затраты времени центрального процессора будут еще больше, чем показанные в строке 1 для параллельного сервера.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКДанный текст является ознакомительным фрагментом.
Читайте также
10.2. Потоковый эхо-сервер SCTP типа «один-ко-многим»: функция main
10.2. Потоковый эхо-сервер SCTP типа «один-ко-многим»: функция main Наши клиент и сервер SCTP вызывают функции в последовательности, представленной на рис. 9.2. Код последовательного сервера представлен в листинге 10.1[1].Листинг 10.1. Потоковый эхо-сервер SCTP//sctp/sctpserv01.c 1 #include "unp.h" 2 int 3
23.2. Сервер типа «один-ко-многим» с автоматическим закрытием
23.2. Сервер типа «один-ко-многим» с автоматическим закрытием Вспомните программу-сервер, которую мы написали в главе 10. Эта программа не отслеживала ассоциации. Сервер рассчитывал, что клиент самостоятельно закроет ассоциацию, удалив тем самым данные о ее состоянии.
30.10. Параллельный сервер TCP: один поток для каждого клиента
30.10. Параллельный сервер TCP: один поток для каждого клиента Предыдущие пять разделов были посвящены рассмотрению серверов, в которых для обработки клиентских запросов используются дочерние процессы, либо заранее порождаемые с помощью функции fork, либо требующие вызова
11.7.2 Доставка запроса от клиента на сервер
11.7.2 Доставка запроса от клиента на сервер Клиент не имеет сведений об адресе для направления запроса и отправляет его с IP-адресом источника 0.0.0.0 и IP-адресом приемника 255.255.255.255.Сервер (или серверы) в одной с клиентом локальной сети услышит посланный запрос. Если клиент
18.10.4. Dante — еще один сервер SOCKS5
18.10.4. Dante — еще один сервер SOCKS5 Этот сервер считается более простым в настройке. Он использует файл конфигурации /etc/sockd.conf (листинг 18.2).Листинг 18.2. Примерный файл /etc/sockd.confinternal: 192.168.0.1 port = 1080external: 111.111.111.111client pass { from: 192.168.0.0/16 to: 0.0.0.0/0}pass { from: 0.0.0.0/0 to: 192.168.0.0/16 command: bindreply udpreply log:
1.5.2. Параллельный поиск
1.5.2. Параллельный поиск Точно не известно, являлся ли Яндекс первопроходцем в создании параллельного поиска или нет, но такой поиск появился на нем одним из первых. Суть его заключается в том, что, помимо основного поиска среди документов, ведется поиск среди
7. Предложение коучинга в мини-группе или один на один
7. Предложение коучинга в мини-группе или один на один Если человек оплатил товар и даже купил у вас что-то еще, это не повод останавливаться. Вы можете позвонить всем клиентам по телефону и предложить бесплатную 15-минутную личную консультацию и продать коучинг один на
Параллельный размер
Параллельный размер С помощью команды DIMALIGNED создается размер, параллельный измеряемой линии объекта; это позволяет выровнять размерную линию по объекту. Размер создается подобно горизонтальному, вертикальному и повернутому, при этом размерная линия расположена
Параллельный размер
Параллельный размер С помощью команды DIMALIGNED создается размер, параллельный измеряемой линии объекта; это позволяет выровнять размерную линию по объекту. Размер создается подобно горизонтальному, вертикальному и повернутому, при этом размерная линия расположена
Параллельный размер
Параллельный размер С помощью команды DIMALIGNED создается размер, параллельный измеряемой линии объекта; это позволяет выровнять размерную линию по объекту. Размер создается подобно горизонтальному, вертикальному и повернутому, при этом размерная линия расположена
PS4 стартовала, Xbox One на подходе: один на один или двое против всех? Евгений Золотов
PS4 стартовала, Xbox One на подходе: один на один или двое против всех? Евгений Золотов Опубликовано 18 ноября 2013 Затянувшееся перемирие в войне игровых консолей окончено: в пятницу в США стартовали продажи Sony PlayStation 4, а её основной соперник, Xbox One от