Литмир - Электронная Библиотека
Содержание  
A
A

Доставка множества экземпляров одного и того же сигнала вызывает проблему, к рассмотрению которой мы и приступим.

UNIX: разработка сетевых приложений - img_41.png

Рис. 5.3. Клиент завершает работу, закрывая все пять соединений и завершая все пять дочерних процессов

Сначала мы запускаем сервер в фоновом режиме, а затем — новый клиент. Наш сервер, показанный в листинге 5.1, несколько модифицирован — теперь в нем вызывается функция

signal
для установки обработчика сигнала
SIGCHLD
, приведенного в листинге 5.6.

linux % <b>tcpserv03 &amp;</b>

[1] 20419

linux % <b>tcpcli04 206.62.226.35</b>

<b>hello </b><i>мы набираем эту строку</i>

hello <i>и она отражается сервером</i>

<b>^D</b>    <i>мы набираем символ конца файла</i>

child 20426 terminated <i>выводится сервером</i>

Первое, что мы можем заметить, — данные выводит только одна функция

printf
, хотя мы предполагаем, что все пять дочерних процессов должны завершиться. Если мы выполним программу
ps
, то увидим, что другие четыре дочерних процесса все еще существуют как зомби.

PID TTY TIME CMD

20419 pts/6 00:00:00 tcpserv03

20421 pts/6 00:00:00 tcpserv03 &lt;defunct&gt;

20422 pts/6 00:00:00 tcpserv03 &lt;defunct&gt;

20423 pts/6 00:00:00 tcpserv03 &lt;defunct&gt;

Установки обработчика сигнала и вызова функции

wait
из этого обработчика недостаточно для предупреждения появления зомби. Проблема состоит в том, что все пять сигналов генерируются до того, как выполняется обработчик сигнала, и вызывается он только один раз, поскольку сигналы Unix обычно не помещаются в очередь. Более того, эта проблема является недетерминированной. В приведенном примере с клиентом и сервером на одном и том же узле обработчик сигнала выполняется один раз, оставляя четыре зомби. Но если мы запустим клиент и сервер на разных узлах, то обработчик сигналов, скорее всего, выполнится дважды: один раз в результате генерации первого сигнала, а поскольку другие четыре сигнала приходят во время выполнения обработчика, он вызывается повторно только один раз. При этом остаются три зомби. Но иногда в зависимости от точного времени получения сегментов FIN на узле сервера обработчик сигналов может выполниться три или даже четыре раза.

Правильным решением будет вызвать функцию

waitpid
вместо
wait
. В листинге 5.8 представлена версия нашей функции
sigchld
, корректно обрабатывающая сигнал
SIGCHLD
. Эта версия работает, потому что мы вызываем функцию
waitpid
в цикле, получая состояние любого из дочерних процессов, которые завершились. Необходимо задать параметр
WNOHANG
: это указывает функции
waitpid
, что не нужно блокироваться, если существуют выполняемые дочерние процессы, которые еще не завершились. В листинге 5.6 мы не могли вызвать функцию
wait
в цикле, поскольку нет возможности предотвратить блокирование функции
wait
при наличии выполняемых дочерних процессов, которые еще не завершились.

В листинге 5.9 показана окончательная версия нашего сервера. Он корректно обрабатывает возвращение ошибки

EINTR
из функции
accept
и устанавливает обработчик сигнала (листинг 5.8), который вызывает функцию
waitpid
для всех завершенных дочерних процессов.

Листинг 5.8. Окончательная (корректная) версия функции sig_chld, вызывающая функцию waitpid

//tcpcliserv/sigchldwaitpid.c

 1 #include &quot;unp.h&quot;

 2 void

 3 sig_chld(int signo)

 4 {

 5  pid_t pid;

 6  int stat;

 7  while ((pid = waitpid(-1, &amp;stat, WNOHANG)) &gt;0)

 8   printf(&quot;child %d terminated\n&quot;, pid);

 9  return;

10 }

Листинг 5.9. Окончательная (корректная) версия TCP-сервера, обрабатывающего ошибку EINTR функции accept

//tcpcliserv/tcpserv04.c

 1 #include &quot;unp.h&quot;

 2 int

 3 main(int argc, char **argv)

 4 {

 5  int listenfd, connfd;

 6  pid_t childpid;

 7  socklen_t clilen;

 8  struct sockaddr_in cliaddr, servaddr;

 9  void sig_chld(int);

10  listenfd = Socket(AF_INET, SOCK_STREAM, 0);

11  bzero(&amp;servaddr, sizeof(servaddr));

12  servaddr.sin_family = AF_INET;

13  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

14  servaddr.sin_port = htons(SERV_PORT);

15  Bind(listenfd, (SA*)&amp;servaddr, sizeof(servaddr));

16  Listen(listenfd, LISTENQ);

17  Signal(SIGCHLD, sig_chld); /* нужно вызвать waitpid() */

18  for (;;) {

19   clilen = sizeof(cliaddr);

20   if ((connfd = accept(listenfd, (SA*)&amp;cliaddr, &amp;clilen)) &lt; 0) {

21    if (errno == EINTR)

22     continue; /* назад к for() */

23    else

24     err_sys(&quot;accept error&quot;);

25   }

26   if ((childpid = Fork()) == 0) { /* дочерний процесс */

55
{"b":"225366","o":1}