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 [
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
.