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

Данный текст является ознакомительным фрагментом.