UNIX: разработка сетевых приложений - Уильям Стивенс
- Дата:12.02.2026
- Категория: Компьютеры и Интернет / Программное обеспечение
- Название: UNIX: разработка сетевых приложений
- Автор: Уильям Стивенс
- Просмотров:1
- Комментариев:0
Аудиокнига "UNIX: разработка сетевых приложений" от Уильяма Стивенса
📚 "UNIX: разработка сетевых приложений" - это увлекательное путешествие в мир UNIX и сетевого программирования. В книге автор подробно рассматривает основные принципы разработки сетевых приложений под UNIX, раскрывая множество интересных тем и примеров.
Главный герой книги - опытный разработчик, который стремится углубить свои знания в области сетевого программирования под UNIX. Он исследует различные аспекты работы с сетью, изучает протоколы передачи данных и на практике применяет полученные знания.
👨💻 Уильям Стивенс - известный специалист в области компьютерных наук, автор множества книг по сетевому программированию. Его работы пользуются популярностью среди разработчиков и студентов по всему миру.
На сайте knigi-online.info вы можете бесплатно и без регистрации слушать аудиокниги онлайн на русском языке. Здесь собраны бестселлеры и лучшие произведения различных жанров, чтобы каждый мог найти что-то по душе.
🎧 Погрузитесь в увлекательный мир UNIX и сетевого программирования вместе с аудиокнигой "UNIX: разработка сетевых приложений" от Уильяма Стивенса. Развивайте свои навыки, открывайте новые горизонты и наслаждайтесь процессом обучения!
Шрифт:
Интервал:
Закладка:
В листинге 30.25 показана функция main.
Листинг 30.25. Функция main для сервера с предварительным порождением потоков
//server/serv08.c
1 #include "unpthread.h"
2 #include "pthread08.h"
3 static int nthreads;
4 pthread_mutex_t clifd_mutex = PTHREAD_MUTEX_INITIALIZER;
5 pthread_cond_t clifd_cond = PTHREAD_COND_INITIALIZER;
6 int
7 main(int argc, char **argv)
8 {
9 int i, listenfd, connfd;
10 void sig_int(int), thread_make(int);
11 socklen_t addrlen, clilen;
12 struct sockaddr *cliaddr;
13 if (argc == 3)
14 listenfd = Tcp_listen(NULL, argv[1], &addrlen);
15 else if (argc == 4)
16 listenfd = Tcp_listen(argv[1], argv[2], &addrlen);
17 else
18 err_quit("usage: serv08 [ <host> ] <port#> <#threads>");
19 cliaddr = Malloc(addrlen);
20 nthreads = atoi(argv[argc - 1]);
21 tptr = Calloc(nthreads, sizeof(Thread));
22 iget = iput = 0;
23 /* создание всех потоков */
24 for (i = 0; i < nthreads; i++)
25 thread_make(i); /* завершается только основной поток */
26 Signal(SIGINT, sig_int);
27 for (;;) {
28 clilen = addrlen;
29 connfd = Accept(listenfd, cliaddr, &clilen);
30 Pthread_mutex_lock(&clifd_mutex);
31 clifd[iput] = connfd;
32 if (++iput == MAXNCLI)
33 iput = 0;
34 if (iput == iget)
35 err_quit("iput = iget = %d", iput);
36 Pthread_cond_signal(&clifd_cond);
37 Pthread_mutex_unlock(&clifd_mutex);
38 }
39 }
Создание пула потоков23-25 Функция thread_make создает все потоки.
Ожидание прихода клиентского соединения27-38 Основной поток блокируется в вызове функции accept, ожидая появления нового соединения. При появлении этого соединения дескриптор присоединенного сокета записывается в следующий элемент массива clifd после блокирования взаимного исключения. Мы также следим, чтобы индекс iget не совпал со значением индекса iput, что укажет на недостаточно большой размер массива. Условная переменная сигнализирует о прибытии нового запроса, и взаимное исключение разблокируется, позволяя одному из потоков пула обслужить прибывший запрос.
Функции thread_make и thread_main показаны в листинге 30.26. Первая из них идентична функции, приведенной в листинге 30.23.
Листинг 30.26. Функции thread_make и thread_main
//server/pthread08.c
1 #include "unpthread.h"
2 #include "pthread08.h"
3 void
4 thread_make(int i)
5 {
6 void *thread_main(void*);
7 Pthread_create(&tptr[i].thread_tid, NULL, &thread_main, (void*)i);
8 return; /* завершается основной поток */
9 }
10 void*
11 thread_main(void *arg)
12 {
13 int connfd;
14 void web_child(int);
15 printf("thread %d startingn", (int)arg);
16 for (;;) {
17 Pthread_mutex_lock(&clifd_mutex);
18 while (iget == iput)
19 Pthread_cond_wait(&clifd_cond, &clifd_mutex);
20 connfd = clifd[iget]; /* присоединенный сокет, который требуется
обслужить */
21 if (++iget == MAXNCLI)
22 iget = 0;
23 Pthread_mutex_unlock(&clifd_mutex);
24 tptr[(int)arg].thread_count++;
25 web_child(connfd); /* обработка запроса */
26 Close(connfd);
27 }
28 }
Ожидание присоединенного сокета, который требует обслуживания17-26 Каждый поток из пула пытается блокировать взаимное исключение, блокирующее доступ к массиву clifd. Если после того, как взаимное исключение заблокировано, оказывается, что индексы iput и iget равны, то вызывается функция pthread_cond_wait, и поток переходит в состояние ожидания, так как ему пока нечего делать. После прибытия очередного клиентского запроса основной поток вызывает функцию pthread_cond_signal, выводя тем самым из состояния ожидания поток, заблокировавший взаимное исключение. Когда этот поток получает соединение, он вызывает функцию web_child.
Значения времени центрального процессора, приведенные в табл. 30.1, показывают, что эта версия сервера медленнее рассмотренной в предыдущем разделе (когда каждый поток из пула сам вызывал функцию accept). Причина заключается в том, что рассматриваемая в данном разделе версия использует как взаимное исключение, так и условную переменную, тогда как в предыдущем случае (см. листинг 30.23) применялось только взаимное исключение.
Если мы рассмотрим гистограмму количества клиентов, обслуживаемых каждым потоком из пула, то окажется, что распределение клиентских запросов по потокам будет таким же, как показано в последнем столбце табл. 30.2. Это означает, что если основной поток вызывает функцию pthread_cond_signal, то при выборе очередного потока, который будет выведен из состояния ожидания для обслуживания клиентского запроса, осуществляется последовательный перебор всех имеющихся свободных потоков.
30.13. Резюме
В этой главе мы рассмотрели 9 различных версий сервера и их работу с одним и тем же веб-клиентом, чтобы сравнить значения времени центрального процессора, затраченного на управление процессом.
0. Последовательный сервер (точка отсчета — управление процессом отсутствует).
1. Параллельный сервер, по одному вызову функции fork для каждого клиента.
2. Предварительное порождение дочерних процессов, каждый из которых вызывает функцию accept.
3. Предварительное порождение дочерних процессов с блокировкой файла для защиты функции accept.
4. Предварительное порождение дочерних процессов с блокировкой взаимного исключения дочерними процессами для защиты функции accept.
5. Предварительное порождение дочерних процессов с передачей дескриптора от родительского процесса дочернему.
6. Параллельный сервер, поочередное создание потоков по мере поступления клиентских запросов.
7. Предварительное порождение потоков с блокировкой взаимного исключения потоками для защиты функции accept.
8. Предварительное порождение потоков, основной поток вызывает функцию accept.
Резюмируя материал этой главы, можно сделать несколько комментариев.
■ Если сервер не слишком загружен, хорошо работает традиционная модель параллельного сервера, в которой при поступлении очередного клиентского запроса вызывается функция fork для создания нового дочернего процесса. Этот вариант допускает комбинирование с демоном inetd, принимающим все клиентские запросы. Остальные версии применимы в случае загруженных серверов, таких как веб-серверы.
■ Создание пула дочерних процессов или потоков сокращает временные затраты центрального процессора по сравнению с традиционной моделью (один вызов функции fork для каждого запроса) в 10 и более раз. При этом не слишком усложняется код, но становится необходимо (как говорилось при обсуждении примеров) отслеживать количество свободных дочерних процессов и корректировать его по мере необходимости, так как количество клиентских запросов, которые требуется обслужить, динамически изменяется.
■ Некоторые реализации допускают блокирование нескольких потоков или дочерних процессов в вызове функции accept, в то время как другие реализации требуют использования блокировки того или иного типа для защиты accept. Можно использовать для этого либо блокировку файла, либо блокировку взаимного исключения Pthreads.
■ Как правило, версия, в которой каждый поток или дочерний процесс вызывает функцию accept, проще и быстрее, чем версия, где вызов функции accept осуществляется только основным потоком (или родительским процессом), впоследствии передающим дескриптор присоединенного сокета другому потоку или дочернему процессу.
■ Блокировка всех дочерних процессов или программных потоков в вызове функции accept предпочтительнее, чем блокировка в вызове функции select, что объясняется возможностью появления коллизий при вызове функции select.
■ Использование потоков, как правило, дает больший выигрыш во времени, чем использование процессов. Но выбор между версиями 1 и 6 (один дочерний процесс на каждый запрос и один поток на каждый запрос) зависит от свойств операционной системы и от того, какие еще программы задействованы в обслуживании клиентских запросов. Например, если сервер, принимающий клиентское соединение, вызывает функции fork и exec, то может оказаться быстрее породить с помощью функции fork процесс с одним потоком, чем процесс с несколькими потоками.
- Изучай Haskell во имя добра! - Миран Липовача - Программирование
- Операционная система UNIX - Андрей Робачевский - Программное обеспечение
- Настоящие программисты не используют Паскаль - Пост Эд - Сатира
- Язык программирования C++. Пятое издание - Стенли Липпман - Программирование
- Вопросы истории: UNIX, Linux, BSD и другие - Федорчук Алексей Викторович "alv" - Прочая околокомпьтерная литература