Библиотека сайта rus-linux.net
Назад | Сервер TCP/IP ... много серверов хороших и разных | Вперед |
Сервер с предварительным созданием копий процесса
Так что же получается: для серверов, работающих на высоко интенсивных потоках запросов, с традиционным fork-методом всё так плохо? Отнюдь! Нужно только поменять вызовы fork() и accept() местами – создать заранее некоторый пул обслуживающих процессов, каждый из которых до прихода клиентского запроса будет заблокирован на accept() (кстати, все accept() на одном и том же прослушиваемом сокете, что не предусмотрено спецификацией, но работает!). А после отработки клиентского запроса заблаговременно создать новый обслуживающий процесс. Эта техника известна как «предварительный fork» или pre-fork. Меняем текст сервера (файл ech11.cc):
#include "common.h" #include <sys/wait.h> const int NUMPROC = 3; // ретранслятор c предварительным fork (pefork) int main( int argc, char *argv[] ) { int ls = getsocket( PREFORK_PORT ), rs; setv( argc, argv ); for( int i = 0; i < NUMPROC; i++ ) { if( fork() == 0 ) { int rs; while( true ) { if( ( rs = accept( ls, NULL, NULL ) ) < 0 ) errx( "accept error" ); retrans( rs ); close( rs ); if( debug ) cout << i << flush; delay( 250 ); // пауза 250 usec. }; }; }; for( int i = 0; i < NUMPROC; i++ ) wait( NULL ); exit( EXIT_SUCCESS ); };
Результаты:
$ sudo nice -n19 ./ech11 -v waiting on port 51003 ... verbose mode 00210210210210210210 $ ./cli -a 192.168.1.5 -p 51003 -n 20 host: 192.168.1.5, TCP port = 51003, number of echoes = 20 time of reply - Cycles [usec.] : 555370[180] 542984[176] 539994[175] 451536[147] 456090[148] 446096[145] 470856[153] 448753[146] 451605[147] 473512[154] 491579[160] 463611[151] 523503[170] 459356[149] 476031[155] 470672[153] 464738[151] 447155[145] 460655[150] 445211[145]
Очень неплохо! Время реакции приближается к последовательному серверу, но сохраняется параллельность, привносимая fork().
При написании этого текста я несколько схитрил и упростил его логику в сравнении с предложенной выше моделью. Здесь три обслуживающих процесса сделаны циклическими и не завершаются по окончанию обслуживания, а снова блокируются на accept(), но для наблюдения эффектов этого вполне достаточно (а последняя строка примера нужна вообще только для блокировки родительского процесса, и сохранения за процессами управляющего терминала — для возможности прекращения всей группы по ^C):
$ ps -A | grep ech 10783 pts/15 00:00:00 ech11 10784 pts/15 00:00:00 ech11 10785 pts/15 00:00:00 ech11 10786 pts/15 00:00:00 ech11
В этом варианте добавлен вывод идентификатора (i) обрабатывающего процесса для идентификации обрабатывающего в каждом случае процесса. Дополнительно добавлена и задержка переактивизации процесса delay(), чтоб заставить обрабатывающие процессы чередоваться. Сервер в примере выше запускался в режиме повышенного уровня диагностики (-v) и можно было наблюдать последовательность активизации обрабатывающих процессов: 00210210210210210210.
В принципе, не так и сложно в такой схеме сделать и динамический пул процессов, как будет обсуждено ниже это для потоков – с той лишь некоторой сложностью, что здесь каждый процесс выполняется в своём закрытом адресном пространстве, и для их взаимной синхронизации придётся использовать что-то из механизмов IPC.
Назад | Сервер TCP/IP ... много серверов хороших и разных | Вперед |