Правильный способ обработки SIGCHLD, EINT и accept() в Linux

У меня есть программа, которая создает TCP-сервер. Когда accept() подключается к клиенту, я fork() его и обрабатываю соединение. Когда этот клиент уходит, он вызывает waitpid() из-за SIGCHLD, но это вызывает EINTR в accept(). Мой вопрос в том, как это должно быть обработано? Я читал так много разных способов.

Большинство советует игнорировать EINT и снова попробовать accept(). Я даже видел макрос для этого: TEMP_FAILURE_RETRY(). Некоторые советуют установить флаги подписи SA_RESTART и SA_NOCLDSTOP. Я пробовал это, и это приводит к другим ошибкам (errno = ECHILD). Кроме того, как ребенок должен выйти? Я видел как _exit(0), так и exit(0).

int main( int argc, char *argv[] )
{
   int sockfd, newsockfd, clilen;
   struct sockaddr_in cli_addr;
   int  pid;

   f_SigHandler();
   sockfd = f_SetupTCPSocket();
   clilen = sizeof(cli_addr);

   while (1)
   {
      newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
      if (newsockfd < 0)
      {
        if( errno == EINTR ) continue;
        else                 exit(1) ;
      }

      pid = fork();
      if (pid == 0)
      {
        close(sockfd);
        doprocessing();
        close(newsockfd);
        _exit(0);
      }
      else
      {
        close(newsockfd);
      }
   }
}

Обработка SIGCHLD:

void f_ChildHandler(int signal_number)
{
  while (waitpid(-1, NULL, WNOHANG) > 0)
  {
  }
}

void f_SigHandler(void)
{
    struct sigaction sa;

    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = f_ChildHandler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGCHLD, &sa, NULL);
}

person Rich G.    schedule 11.09.2015    source источник
comment
Если вам не нужно получать статус выхода, вы можете игнорировать SIGCHLD. См. stackoverflow.com/questions/ 17015830/   -  person Barmar    schedule 11.09.2015


Ответы (1)


В вашем случае, вероятно, достаточно простого SA_RESTART и waitpid() в обработчике. Когда код выхода неинтересен, можно дополнительно передать SA_NOCLDWAIT.

Когда выход клиента должен быть обработан более сложным образом, вы можете поймать EINTR в основной программе и вызвать там waitpid(). Чтобы сделать его свободным от гонки, вы должны использовать pselect(), чтобы заблокировать сигнал. Кроме того, вы можете создать signalfd и использовать его в select/poll с вашим sockfd.

Дочерний элемент должен использовать _exit() для предотвращения выполнения обработчиков atexit() (например, которые записывают записи завершения в файл).

person ensc    schedule 11.09.2015
comment
Когда существует один дочерний элемент и этот дочерний элемент _exit(), вызывается f_ChildHandler(). В первый раз через цикл while waitpid() возвращает pid только что вышедшего дочернего элемента. Во второй раз функция waitpid() возвращает -1 и устанавливает для errno значение 10 (ECHILD). Я ожидал этого, потому что детей больше нет, и именно это должен делать waitpid(), когда детей больше нет. Итак, если я хочу работать без ошибок, должен ли я просто вызвать waitpid() один раз в f_ChildHandler() и установить SA_RESTART? - person Rich G.; 17.09.2015