30.10. Параллельный сервер TCP: один поток для каждого клиента
30.10. Параллельный сервер TCP: один поток для каждого клиента
Предыдущие пять разделов были посвящены рассмотрению серверов, в которых для обработки клиентских запросов используются дочерние процессы, либо заранее порождаемые с помощью функции fork, либо требующие вызова этой функции для каждого вновь поступившего клиентского запроса. Если же сервер поддерживает потоки, мы можем применить потоки вместо дочерних процессов.
Наша первая версия сервера с использованием потоков показана в листинге 30.20. Это модификация листинга 30.2: в ней создается один поток для каждого клиента вместо одного дочернего процесса для каждого клиента. Эта версия во многом похожа на сервер, представленный в листинге 26.2.
Листинг 30.20. Функция main для сервера TCP, использующего потоки
//server/serv06.c
1 #include "unpthread.h"
2 int
3 main(int argc, char **argv)
4 {
5 int listenfd, connfd;
6 void sig_int(int);
7 void *doit(void*);
8 pthread_t tid;
9 socklen_t clilen, addrlen;
10 struct sockaddr *cliaddr;
11 if (argc == 2)
12 listenfd = Tcp_listen(NULL, argv[1], &addrlen);
13 else if (argc == 3)
14 listenfd = Tcp_listen(argv[1], argv[2], &addrlen);
15 else
16 err_quit("usage: serv06 [ <host> ] <port#>");
17 cliaddr = Malloc(addrlen);
18 Signal (SIGINT, sig_int);
19 for (;;) {
20 clilen = addrlen;
21 connfd = Accept(listenfd, cliaddr, &clilen);
22 Pthread_create(&tid, NULL, &doit, (void*)connfd);
23 }
24 }
25 void*
26 doit(void *arg)
27 {
28 void web_child(int);
29 Pthread_detach(pthread_self());
30 web_child((int)arg);
31 Close((int)arg);
32 return (NULL);
33 }
Цикл основного потока
19-23 Основной поток блокируется в вызове функции accept, и каждый раз, когда прибывает новое клиентское соединение, функцией pthread_create создается новый поток. Функция, выполняемая новым потоком, — это функция doit, а ее аргументом является присоединенный сокет.
Функция прочих потоков
25-33 Функция doit выполняется как отсоединенный (detached) поток, потому что основному потоку не требуется ждать ее завершения. Doit вызывает функцию web_child (см. листинг 30.5). Когда эта функция возвращает управление, присоединенный сокет закрывается.
Из табл. 30.1 мы видим, что эта простая версия с использованием потоков является более быстродействующей, чем даже самая быстрая из версий с предварительным порождением процессов. Кроме того, эта версия, в которой каждый клиент обслуживается одним потоком, во много раз быстрее версии, в которой каждый клиент обслуживается специально созданным для него дочерним процессом (первая строка табл. 30.1).
ПРИМЕЧАНИЕ
В разделе 26.5 мы упомянули о трех вариантах преобразования функции, которая не является безопасной в многопоточной среде, в функцию, обеспечивающую требуемую безопасность. Функция web_child вызывает функцию readline, и версия, показанная в листинге 3.12, не является безопасной в многопоточной среде. На примере, приведенном в листинге 30.20, были испробованы вторая и третья альтернативы из раздела 26.5. Увеличение быстродействия при переходе от альтернативы 3 к альтернативе 2 составило менее одного процента, вероятно, потому, что функция readline использовалась лишь для считывания значения счетчика (5 символов) от клиента. Поэтому в данной главе для простоты мы использовали более медленную версию из листинга 3.11 для сервера с предварительным порождением потоков.
Данный текст является ознакомительным фрагментом.