Создается поток, и значение нового идентификатора потока сохраняется в
tid
. Функция, выполняемая новым потоком, — это
copyto
. Никакие аргументы потоку не передаются.
Главный цикл потока: копирование из сокета в стандартный поток вывода
13-14
В основном цикле вызываются функции
readline
и
fputs
, которые осуществляют копирование из сокета в стандартный поток вывода.
Завершение
15
Когда функция
str_cli
возвращает управление, функция main завершается при помощи вызова функции
exit
(см. раздел 5.4). При этом завершаются все потоки данного процесса. В обычном сценарии второй поток уже должен завершиться в результате считывания признака конца файла из стандартного потока ввода. Но в случае, когда сервер преждевременно завершил свою работу (см. раздел 5.12), при вызове функции
exit
завершается также и второй поток, чего мы и добиваемся.
Поток copyto
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*); /* каждый поток выполняет эту функцию */
28 Close((int)arg); /* мы закончили с присоединенным сокетом */
29 return (NULL);
30 }
Создание потока
17-21
Когда функция
accept
возвращает управление, мы вызываем функцию
pthread_create
вместо функции
fork
. Мы передаем функции
doit
единственный аргумент — дескриптор присоединенного сокета
connfd
.
ПРИМЕЧАНИЕ
Мы преобразуем целочисленный дескриптор сокета к универсальному указателю (void). В ANSI С не гарантируется, что такое преобразование будет выполнено корректно, — мы можем быть уверены лишь в том, что оно сработает в тех системах, в которых размер целого числа не превышает размера указателя. К счастью, большинство реализаций Unix обладают этим свойством (см. табл. 1.5). Далее мы поговорим об этом подробнее.
Функция потока
23-30
doit
— это функция, выполняемая потоком. Поток отделяет себя с помощью функции
pthread_detach
, так как нет причины, по которой главному потоку имело бы смысл ждать завершения каждого созданного им потока. Функция
str_echo
не изменилась и осталась такой же, как в листинге 5.2. Когда эта функция завершается, следует вызвать функцию
close
для того, чтобы закрыть присоединенный сокет, поскольку этот поток использует все дескрипторы совместно с главным потоком. При использовании функции
fork
дочерний процесс не должен специально закрывать присоединенный сокет, так как при завершении дочернего процесса все открытые дескрипторы закрываются (см. упражнение 26.2).