Уильям Стивенс - UNIX: разработка сетевых приложений
- Название:UNIX: разработка сетевых приложений
- Автор:
- Жанр:
- Издательство:Питер
- Год:2007
- Город:Санкт-Петербург
- ISBN:5-94723-991-4
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Уильям Стивенс - UNIX: разработка сетевых приложений краткое содержание
Новое издание книги, посвященной созданию веб-серверов, клиент-серверных приложений или любого другого сетевого программного обеспечения в операционной системе UNIX, — классическое руководство по сетевым программным интерфейсам, в частности сокетам. Оно основано на трудах Уильяма Стивенса и полностью переработано и обновлено двумя ведущими экспертами по сетевому программированию. В книгу включено описание ключевых современных стандартов, реализаций и методов, она содержит большое количество иллюстрирующих примеров и может использоваться как учебник по программированию в сетях, так и в качестве справочника для опытных программистов.
UNIX: разработка сетевых приложений - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
Обратите внимание, что мы вызываем функцию accept
, а не функцию-обертку Accept
, поскольку мы должны обработать неудачное выполнение функции самостоятельно.
В этой части кода мы сами перезапускаем прерванный системный вызов. Это допустимо для функции accept
и таких функций, как read
, write
, select
и open
. Но есть функция, которую мы не можем перезапустить самостоятельно, — это функция connect
. Если она возвращает ошибку EINTR
, мы не можем снова вызвать ее, поскольку в этом случае немедленно возвратится еще одна ошибка. Когда функция connect прерывается перехваченным сигналом и не перезапускается автоматически, нужно вызвать функцию select
, чтобы дождаться завершения соединения (см. раздел 16.3).
5.10. Функции wait и waitpid
В листинге 5.7 мы вызываем функцию wait
для обработки завершенного дочернего процесса.
#include
pid_t wait(int * statloc );
pid_t waitpid(pid_t pid , int * statloc , int options );
Обе функции возвращают ID процесса в случае успешного выполнения, -1 в случае ошибки
Обе функции, и wait
, и waitpid
, возвращают два значения. Возвращаемое значение каждой из этих функций — это идентификатор завершенного дочернего процесса, а через указатель statloc
передается статус завершения дочернего процесса (целое число). Для проверки статуса завершения можно вызвать три макроса, которые сообщают нам, что произошло с дочерним процессом: дочерний процесс завершен нормально, уничтожен сигналом или только приостановлен программой управления заданиями (job-control). Дополнительные макросы позволяют получить состояние выхода дочернего процесса, а также значение сигнала, уничтожившего или остановившего процесс. В листинге 15.8 мы используем макроопределения WIFEXITED
и WEXITSTATUS
.
Если у процесса, вызывающего функцию wait
, нет завершенных дочерних процессов, но есть один или несколько выполняющихся, функция wait
блокируется до тех пор, пока первый из дочерних процессов не завершится.
Функция waitpid
предоставляет более гибкие возможности выбора ожидаемого процесса и его блокирования. Прежде всего, в аргументе pid
задается идентификатор процесса, который мы будем ожидать. Значение -1 говорит о том, что нужно дождаться завершения первого дочернего процесса. (Существуют и другие значения идентификаторов процесса, но здесь они нам не понадобятся.) Аргумент options
позволяет задавать дополнительные параметры. Наиболее общеупотребительным является параметр WNOHANG
: он сообщает ядру, что не нужно выполнять блокирование, если нет завершенных дочерних процессов.
Различия между функциями wait и waitpid
Теперь мы проиллюстрируем разницу между функциями wait
и waitpid
, используемыми для сброса завершенных дочерних процессов. Для этого мы изменим код нашего клиента TCP так, как показано в листинге 5.7. Клиент устанавливает пять соединений с сервером, а затем использует первое из них ( sockfd[0]
) в вызове функции str_cli
. Несколько соединений мы устанавливаем для того, чтобы породить от параллельного сервера множество дочерних процессов, как показано на рис. 5.2.

Рис. 5.2. Клиент, установивший пять соединений с одним и тем же параллельным сервером
Листинг 5.7. Клиент TCP, устанавливающий пять соединений с сервером
/ /tcpcliserv/tcpcli04.c
1 #include "unp.h"
2 int
3 main(int argc, char **argv)
4 {
5 int i, sockfd[5];
6 struct sockaddr_in servaddr;
7 if (argc != 2)
8 err_quit("usage: tcpcli ");
9 for (i = 0; i < 5; i++) {
10 sockfd[i] = Socket(AF_INET, SOCK_STREAM, 0);
11 bzero(&servaddr, sizeof(servaddr));
12 servaddr.sin_family = AF_INET;
13 servaddr.sin_port = htons(SERV_PORT);
14 Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
15 Connect(sockfd[i], (SA*)&servaddr, sizeof(servaddr));
16 }
17 str_cli(stdin, sockfd[0]); /* эта функция выполняет все необходимые
действия для формирования запроса клиента */
18 exit(0);
19 }
Когда клиент завершает работу, все открытые дескрипторы автоматически закрываются ядром (мы не вызываем функцию close ,
а пользуемся только функцией exit
) и все пять соединений завершаются приблизительно в одно и то же время. Это вызывает отправку пяти сегментов FIN, по одному на каждое соединение, что, в свою очередь, вызывает примерно одновременное завершение всех пяти дочерних процессов. Это приводит к доставке пяти сигналов SIGCHLD
практически в один и тот же момент, что показано на рис. 5.3.
Доставка множества экземпляров одного и того же сигнала вызывает проблему, к рассмотрению которой мы и приступим.

Рис. 5.3. Клиент завершает работу, закрывая все пять соединений и завершая все пять дочерних процессов
Сначала мы запускаем сервер в фоновом режиме, а затем — новый клиент. Наш сервер, показанный в листинге 5.1, несколько модифицирован — теперь в нем вызывается функция signal
для установки обработчика сигнала SIGCHLD
, приведенного в листинге 5.6.
linux % tcpserv03 &
[1] 20419
linux % tcpcli04 206.62.226.35
hello мы набираем эту строку
hello и она отражается сервером
^D мы набираем символ конца файла
child 20426 terminated выводится сервером
Первое, что мы можем заметить, — данные выводит только одна функция printf
, хотя мы предполагаем, что все пять дочерних процессов должны завершиться. Если мы выполним программу ps
, то увидим, что другие четыре дочерних процесса все еще существуют как зомби.
PID TTY TIME CMD
20419 pts/6 00:00:00 tcpserv03
20421 pts/6 00:00:00 tcpserv03
20422 pts/6 00:00:00 tcpserv03
20423 pts/6 00:00:00 tcpserv03
Установки обработчика сигнала и вызова функции wait
из этого обработчика недостаточно для предупреждения появления зомби. Проблема состоит в том, что все пять сигналов генерируются до того, как выполняется обработчик сигнала, и вызывается он только один раз, поскольку сигналы Unix обычно не помещаются в очередь. Более того, эта проблема является недетерминированной. В приведенном примере с клиентом и сервером на одном и том же узле обработчик сигнала выполняется один раз, оставляя четыре зомби. Но если мы запустим клиент и сервер на разных узлах, то обработчик сигналов, скорее всего, выполнится дважды: один раз в результате генерации первого сигнала, а поскольку другие четыре сигнала приходят во время выполнения обработчика, он вызывается повторно только один раз. При этом остаются три зомби. Но иногда в зависимости от точного времени получения сегментов FIN на узле сервера обработчик сигналов может выполниться три или даже четыре раза.
Интервал:
Закладка: