Книга: UNIX: разработка сетевых приложений

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 для сервера с предварительным порождением потоков.

Оглавление книги


Генерация: 1.319. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз