UNIX: разработка сетевых приложений
Чтобы обработать прерванный вызов функции
, мы изменяем вызов функцииaccept, приведенной в листинге 5.1, в начале циклаacceptследующим образом:forfor (;;) {clilen = sizeof(cliaddr);if ((connfd = accept(listenfd, (SA*)&cliaddr, &clilen)) < 0) {if (errno == EINTR)continue; /* назад в for() */elseerr_sys("accept error");}Обратите внимание, что мы вызываем функцию
, а не функцию-оберткуaccept, поскольку мы должны обработать неудачное выполнение функции самостоятельно.AcceptВ этой части кода мы сами перезапускаем прерванный системный вызов. Это допустимо для функции
и таких функций, какaccept,read,writeиselect. Но есть функция, которую мы не можем перезапустить самостоятельно, — это функцияopen. Если она возвращает ошибкуconnect, мы не можем снова вызвать ее, поскольку в этом случае немедленно возвратится еще одна ошибка. Когда функция connect прерывается перехваченным сигналом и не перезапускается автоматически, нужно вызвать функциюEINTR, чтобы дождаться завершения соединения (см. раздел 16.3).select5.10. Функции wait и waitpid
В листинге 5.7 мы вызываем функцию
для обработки завершенного дочернего процесса.wait#include <sys/wait.h>pid_t wait(int *<i>statloc</i>);pid_t waitpid(pid_t <i>pid</i>, int *<i>statloc</i>, int <i>options</i>);<i>Обе функции возвращают ID процесса в случае успешного выполнения, -1 в случае ошибки</i>Обе функции, и
, иwait, возвращают два значения. Возвращаемое значение каждой из этих функций — это идентификатор завершенного дочернего процесса, а через указательwaitpidпередается статус завершения дочернего процесса (целое число). Для проверки статуса завершения можно вызвать три макроса, которые сообщают нам, что произошло с дочерним процессом: дочерний процесс завершен нормально, уничтожен сигналом или только приостановлен программой управления заданиями (job-control). Дополнительные макросы позволяют получить состояние выхода дочернего процесса, а также значение сигнала, уничтожившего или остановившего процесс. В листинге 15.8 мы используем макроопределенияstatlocиWIFEXITED.WEXITSTATUSЕсли у процесса, вызывающего функцию
, нет завершенных дочерних процессов, но есть один или несколько выполняющихся, функцияwaitблокируется до тех пор, пока первый из дочерних процессов не завершится.waitФункция
предоставляет более гибкие возможности выбора ожидаемого процесса и его блокирования. Прежде всего, в аргументеwaitpidзадается идентификатор процесса, который мы будем ожидать. Значение -1 говорит о том, что нужно дождаться завершения первого дочернего процесса. (Существуют и другие значения идентификаторов процесса, но здесь они нам не понадобятся.) Аргументpidпозволяет задавать дополнительные параметры. Наиболее общеупотребительным является параметрoptions: он сообщает ядру, что не нужно выполнять блокирование, если нет завершенных дочерних процессов.WNOHANGРазличия между функциями wait и waitpid
Теперь мы проиллюстрируем разницу между функциями
иwait, используемыми для сброса завершенных дочерних процессов. Для этого мы изменим код нашего клиента TCP так, как показано в листинге 5.7. Клиент устанавливает пять соединений с сервером, а затем использует первое из них (waitpid) в вызове функцииsockfd[0]. Несколько соединений мы устанавливаем для того, чтобы породить от параллельного сервера множество дочерних процессов, как показано на рис. 5.2.str_cliРис. 5.2. Клиент, установивший пять соединений с одним и тем же параллельным сервером
Листинг 5.7. Клиент TCP, устанавливающий пять соединений с сервером
/
/tcpcliserv/tcpcli04.c1 #include "unp.h"2 int3 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 <Ipaddress>");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
а пользуемся только функцией,) и все пять соединений завершаются приблизительно в одно и то же время. Это вызывает отправку пяти сегментов FIN, по одному на каждое соединение, что, в свою очередь, вызывает примерно одновременное завершение всех пяти дочерних процессов. Это приводит к доставке пяти сигналовexitпрактически в один и тот же момент, что показано на рис. 5.3.SIGCHLD
