12
tid
. Функция, выполняемая новым потоком, — это
copyto
. Никакие аргументы потоку не передаются.13-14
readline
и
fputs
, которые осуществляют копирование из сокета в стандартный поток вывода.15
str_cli
возвращает управление, функция main завершается при помощи вызова функции
exit
(см. раздел 5.4). При этом завершаются все потоки данного процесса. В обычном сценарии второй поток уже должен завершиться в результате считывания признака конца файла из стандартного потока ввода. Но в случае, когда сервер преждевременно завершил свою работу (см. раздел 5.12), при вызове функции
exit
завершается также и второй поток, чего мы и добиваемся.16-25
shutdown
и отсылается сегмент FIN, после чего поток возвращает управление. При выполнении оператора
return
(то есть когда функция, запустившая поток, возвращает управление) поток также завершается.В конце раздела 16.2 мы привели результаты измерений времени выполнения для пяти различных реализаций функции
str_cli
. Мы отметили, что многопоточная версия выполняется всего 8,5 с — немногим быстрее, чем версия, использующая функцию
fork
(как мы и ожидали), но медленнее, чем версия с неблокируемым вводом-выводом. Тем не менее, сравнивая устройство версии с неблокируемым вводом-выводом (см. раздел 16.2) и версии с использованием потоков, мы заметили, что первая гораздо сложнее. Поэтому мы рекомендуем использовать именно версию с потоками, а не с неблокируемым вводом-выводом.26.4. Использование потоков в эхо-сервере TCP
Теперь мы перепишем эхо-сервер TCP, приведенный в листинге 5.1, используя для каждого клиента по одному потоку вместо одного процесса. Кроме того, с помощью нашей функции
tcp_listen
мы сделаем эту версию не зависящей от протокола. В листинге 26.2 показан код сервера.Листинг 26.2
. Эхо-сервер TCP, использующий потоки//threads/tcpserv01.с
1 #include "unpthread.h"
2 static void *doit(void*); /* каждый поток выполняет эту функцию */
3 int
4 main(int argc, char **argv)
5 {
6 int listenfd, connfd;
7 pthread_t tid;
8 socklen_t addrlen, len;
9 struct sockaddr *cliaddr;
10 if (argc == 2)
11 listenfd = Tcp_listen(NULL, argv[1], addrlen);
12 else if (argc == 3)
13 listenfd = Tcp_listen(argv[1], argv[2], addrlen);
14 else
15 err_quit("usage: tcpserv01 [ host ] service or port");
16 cliaddr = Malloc(addrlen);
17 for (;;) {
18 len = addrlen;
19 connfd = Accept(listenfd, cliaddr, len);
20 Pthread_create(tid, NULL, doit, (void*)connfd);
21 }
22 }
23 static void*
24 doit(void *arg)
25 {
26 Pthread_detach(pthread_self);
27 str_echo((int)arg); /* та же функция, что и раньше */
28 Close((int)arg); /* мы закончили с присоединенным сокетом */
29 return (NULL);
30 }
17-21
accept
возвращает управление, мы вызываем функцию
pthread_create
вместо функции
fork
. Мы передаем функции
doit
единственный аргумент — дескриптор присоединенного сокета
connfd
.