Книга: UNIX: разработка сетевых приложений
30.1. Введение
30.1. Введение
При написании сервера под Unix мы можем выбирать из следующих вариантов управления процессом:
? Наш первый сервер, показанный в листинге 1.5, был последовательным (iterative), но количество сценариев, для которых этот вариант является предпочтительным, весьма ограничено, поскольку последовательный сервер не может начать обработку очередного клиентского запроса, не закончив полностью обработку текущего запроса.
? В листинге 5.1 показан первый в данной книге параллельный (concurrent) сервер, который для обработки каждого клиентского запроса порождал дочерний процесс с помощью функции fork
. Традиционно большинство серверов, работающих под Unix, попадают в эту категорию.
? В разделе 6.8 мы разработали другую версию сервера TCP, в котором имеется только один процесс, обрабатывающий любое количество клиентских запросов с помощью функции select
.
? В листинге 26.2 мы модифицировали параллельный сервер, создав для каждого клиента по одному потоку вместо одного процесса.
В этой главе мы рассмотрим два других способа модификации устройства параллельного сервера.
? Предварительное создание дочерних процессов (preforking). В этом случае при запуске сервера выполняется функция fork
, которая создает определенное количество (пул) дочерних процессов. Обработкой очередного клиентского запроса занимается процесс, взятый из этого набора.
? Предварительное создание потоков (prethreading). При запуске сервера создается некоторое количество (пул) потоков, и для обработки каждого клиента используется поток из данного набора.
В данной главе мы будем рассматривать множество вопросов, связанных с предварительным созданием потоков и процессов. Например, что произойдет, если в пуле окажется недостаточное количество процессов или потоков? А если их будет слишком много? Как родительский и дочерние процессы (или потоки) синхронизируют свои действия?
Обычно написать клиент легче, чем сервер, за счет простоты управления процессом клиента. Тем не менее мы уже исследовали различные способы написания простого эхо-клиента, которые вкратце изложены в разделе 30.2.
В этой главе мы рассматриваем девять различных способов устройства сервера и взаимодействие каждого из этих серверов с одним и тем же клиентом. Клиент-серверный сценарий типичен для WWW: клиент посылает небольшой по объему запрос, а сервер отвечает ему, отсылая соответствующие запросу данные. Некоторые из этих серверов мы уже достаточно подробно обсуждали (например, параллельный сервер, вызывающий функцию fork
для обработки каждого клиентского запроса), в то время как предварительное создание процессов и потоков являются новыми для нас концепциями, которые и будут подробно рассмотрены в этой главе.
Мы запускали различные экземпляры клиента с каждым сервером, измеряя время, которое процессор тратит на обслуживание определенного количества клиентских запросов. Чтобы информация об этом не оказалась рассеянной по всей главе, мы свели все полученные результаты в табл. 30.1, на которую в этой главе будем неоднократно ссылаться. Следует отметить, что значения времени, указанные в этой таблице, соответствуют процессорному времени, затраченному только на управление процессом, так как из фактического значения времени процессора мы вычитаем время, которое тратит на выполнение того же задания последовательный сервер, не имеющий накладных расходов, связанных с управлением процессом. Иными словами, нулевой точкой отсчета в данной таблице для нас является время, затраченное последовательным сервером. Для большей наглядности мы включили в таблицу строку для последовательного сервера с нулевыми значениями времени. В этой главе термином время центрального процессора на управление процессом (process control CPU time) мы обозначаем разность между фактическим значением времени центрального процессора и временем, затраченным последовательным сервером, для каждой конкретной системы.
Таблица 30.1. Сравнительные значения времени, затраченного каждым из обсуждаемых в данной главе сервером
Описание сервера | Время центрального процессора на управление процессом | |
---|---|---|
0 | Последовательный (точка отсчета; затраты на управление процессом отсутствуют) | 0,0 |
1 | Параллельный сервер, один вызов функции fork для обработки одного клиента | 20,90 |
2 | Предварительное создание дочерних процессов, каждый из которых вызывает функцию accept | 1,80 |
3 | Предварительное создание дочерних процессов с блокировкой для защиты accept | 2,07 |
4 | Предварительное создание дочерних процессов с использованием взаимного исключения для защиты accept | 1,75 |
5 | Предварительное создание дочерних процессов, родительский процесс передает дочернему дескриптор сокета | 2,58 |
6 | Параллельный сервер, создание одного потока на каждый клиентский запрос | 0,99 |
7 | Предварительное создание потоков с использованием взаимного исключения для защиты accept | 1,93 |
8 | Предварительное создание потоков, главный поток вызывает accept | 2,05 |
Все приведенные выше значения времени были получены путем запуска клиента, показанного в листинге 30.1, на двух различных узлах в той же подсети, что и сервер. Во всех тестах оба клиента порождали пять дочерних процессов для создания пяти одновременных соединений с сервером, таким образом максимальное количество одновременных соединений с сервером было равно 10. Каждый клиент запрашивал 4000 байт данных от сервера по каждому соединению. В случае, когда тест подразумевает предварительное создание дочерних процессов или потоков при запуске сервера, их количество равно 15.
Некоторые версии нашего сервера работали с предварительно созданным пулом потоков или процессов. Интересным моментом является распределение клиентских запросов по потокам или дочерним процессам, находящимся в накопителе. В табл. 30.2 показаны варианты этого распределения, которые также будут обсуждаться в соответствующих разделах.
Таблица 30.2. Количество клиентов, обслуженных каждым из 15 дочерних процессов или потоков
№ процесса или потока | Предварительное создание процессов без защиты accept (строка 2) | Предварительное создание процессов с защитой accept (строка 3) | Предварительное создание процессов, передача дескриптора (строка 5) | Предварительное порождение потоков, защита accept (строка 7) |
---|---|---|---|---|
0 | 333 | 347 | 1006 | 333 |
1 | 340 | 328 | 950 | 323 |
2 | 335 | 332 | 720 | 333 |
3 | 335 | 335 | 583 | 328 |
4 | 332 | 338 | 485 | 329 |
5 | 331 | 340 | 457 | 322 |
6 | 333 | 335 | 385 | 324 |
7 | 333 | 343 | 250 | 360 |
8 | 332 | 324 | 105 | 341 |
9 | 331 | 315 | 32 | 348 |
10 | 334 | 326 | 14 | 358 |
11 | 333 | 340 | 9 | 331 |
12 | 334 | 330 | 4 | 321 |
13 | 332 | 331 | 1 | 329 |
14 | 332 | 336 | 0 | 320 |
5000 | 5000 | 5000 | 5000 |
- 30.1. Введение
- 30.2. Альтернативы для клиента TCP
- 30.3. Тестовый клиент TCP
- 30.4. Последовательный сервер TCP
- 30.5. Параллельный сервер TCP: один дочерний процесс для каждого клиента
- 30.6. Сервер TCP с предварительным порождением процессов без блокировки для вызова accept
- 30.7. Сервер TCP с предварительным порождением процессов и защитой вызова accept блокировкой файла
- 30.8. Сервер TCP с предварительным порождением процессов и защитой вызова accept при помощи взаимного исключения
- 30.9. Сервер TCP с предварительным порождением процессов: передача дескриптора
- 30.10. Параллельный сервер TCP: один поток для каждого клиента
- 30.11. Сервер TCP с предварительным порождением потоков, каждый из которых вызывает accept
- 30.12. Сервер с предварительным порождением потоков: основной поток вызывает функцию accept
- 30.13. Резюме
- Упражнения