Шрифт:
встречается признак конца файла */
25 }
Заголовочный файл unpthread.h
1
Мы впервые встречаемся с заголовочным файлом unpthread.h
. Он включает наш обычный заголовочный файл unp.h
, затем — заголовочный файл POSIX <pthread.h>
, и далее определяет прототипы наших потоковых функций-оберток для pthread_XXX
(см. раздел 1.4), название каждой из которых начинается с Pthread_
. Сохранение аргументов во внешних переменных
10-11
Для потока, который мы собираемся создать, требуются значения двух аргументов функции str_cli
: fp
— указатель на структуру FILE
для входного файла, и sockfd
— сокет TCP, связанный с сервером. Для простоты мы храним эти два значения во внешних переменных. Альтернативой является запись этих двух значений в структуру, указатель на которую затем передается в качестве аргумента создаваемому потоку. Создание нового потока
12
Создается поток, и значение нового идентификатора потока сохраняется в 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*); /* каждый поток выполняет эту функцию */
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);