The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]




Версия для распечатки Пред. тема | След. тема
Новые ответы [ Отслеживать ]
fork и recv, !*! Serega_S, 17-Окт-05, 19:16  [смотреть все]
В одном из потоков я в цикле читаю из сокета. Получив данные, запускаю fork и в дочернем потоке через execvp() запускаю прогрмму-отбражения данных.
  Бага: как только завершается программа-отображения в лог главной прогрммы сыпется:
Ошибка вызова recv - номер ошибки 4 (Interrupted system call)
Программа пытается вновь присоединиться и:
Ошибка вызова connect - номер ошибки 9 (Bad file descriptor)

проходя в отладчике - всё работает нормально, кроме разве что запуска программы через execvp (по идеее она в бэкграунде должна была запуститься).
Насколько я понимаю, после fork создаётся копия процесса и вызвав в дочернем процессе execvp контекст дочернего процесса заменяется контекстом, запускаемой там программы. Т.е. если та программа завершится, то и процесс завершиться (дочерний). И это не коим образом не должно влиять на родительский процесс и уж тем более не должен выполняться код радительского процесса.

  Поэтому вопрос - почему завершение дочернего процесса, с заменённым контекстом задачи, влияет на родительский процесс?

P.S. Я бы привёл код, но там его слишком много. И размазан он по файлу.

  • fork и recv, !*! jd, 05:22 , 18-Окт-05 (1)
    Вариантов может быть много. Например, Interrupted system call может быть, если стоит обработчик сигнала SIGCHLD. А connect может вернуть ошибку Bad file descriptor, например, если сокет, который ему передаётся уже закрыт...

    Словом, без кода можно строить предположения до бесконечности. Если он большой, его можно порезать, оставив только то, что относится к делу.

    • fork и recv, !*! Serega_S, 12:20 , 18-Окт-05 (2)
      >Вариантов может быть много. Например, Interrupted system call может быть, если стоит
      >обработчик сигнала SIGCHLD. А connect может вернуть ошибку Bad file descriptor,
      >например, если сокет, который ему передаётся уже закрыт...
      >
      >Словом, без кода можно строить предположения до бесконечности. Если он большой, его
      >можно порезать, оставив только то, что относится к делу.

      Фактически код такой:

      main()
      {
        // Регистрация обработчика сообщения    
        struct sigaction sa;
        memset(&sa,0,sizeof(sa));
        sa.sa_handler=&handler_CHLD;
        if(sigaction(SIGCHLD,&sa,0)==-1)
        {
           // выход
           set_log("Ошибка регистрации обработчика сигнала",errno);
           return -1;
        }
        if(c.connect()==-1) // c - класс tcp клиента
        {
           // ошибка
        }
        while(!flags.flag_off)
        {
            t=c.read((char*)&m,sizeof(m));
            pam=(char*)malloc(m.len_data+1); // пропишем там 0
            t=c.read(pam,m.len_data);
            *(pam+m.len_data)=0;
            // Запускаем звук:
            char**par=(char**)malloc(3*sizeof(char*));
            if((int)par==0)break;
            *(par+0)="esdplay";
            *(par+1)="./audio/Message.wav";
            *(par+2)=0;
            int pid=fork();
            if(!pid)
            {
            // дитя
            execvp("esdplay",par);                            //return 0; // пробовал и так (мало ли)
            }
            else
            {
            freemem((char**)&par);
            }
        }
      }
      // Так же есть такой обработчик
      void handler_CHLD(int signal_num)
      {
          int status=0;
          int t=wait(&status);
      }


      Может быть я что-то упустил - я могу выслать весь код проекта, но вроде всё что надо написал... Если слать код - там примерно 130 Кб неужатого текста, так что не очень много в архиве будет... :-)

      • fork и recv, !*! jd, 14:00 , 18-Окт-05 (3)
        >>Вариантов может быть много. Например, Interrupted system call может быть, если стоит
        >>обработчик сигнала SIGCHLD. А connect может вернуть ошибку Bad file descriptor,
        >>например, если сокет, который ему передаётся уже закрыт...
        >>
        >>Словом, без кода можно строить предположения до бесконечности. Если он большой, его
        >>можно порезать, оставив только то, что относится к делу.
        >
        >Фактически код такой:
        >
        >main()
        >{
        >  // Регистрация обработчика сообщения
        >  struct sigaction sa;
        >  memset(&sa,0,sizeof(sa));
        >  sa.sa_handler=&handler_CHLD;
        >  if(sigaction(SIGCHLD,&sa,0)==-1)
        >  {
        >     // выход
        >     set_log("Ошибка регистрации обработчика сигнала",errno);
        >     return -1;
        >  }
        >  if(c.connect()==-1) // c - класс tcp клиента
        >  {
        >     // ошибка
        >  }
        >  while(!flags.flag_off)
        >  {
        >      t=c.read((char*)&m,sizeof(m));
        >      pam=(char*)malloc(m.len_data+1); // пропишем там 0
        >      t=c.read(pam,m.len_data);
        >      *(pam+m.len_data)=0;
        >      // Запускаем звук:
        >      char**par=(char**)malloc(3*sizeof(char*));
        >      if((int)par==0)break;
        >      *(par+0)="esdplay";
        >      *(par+1)="./audio/Message.wav";
        >      *(par+2)=0;
        >      int pid=fork();
        >      if(!pid)
        >      {
        >   // дитя
        >   execvp("esdplay",par);       //return 0;
        >// пробовал и так (мало ли)
        >      }
        >      else
        >      {
        >   freemem((char**)&par);
        >      }
        >  }
        >}
        >// Так же есть такой обработчик
        >void handler_CHLD(int signal_num)
        >{
        > int status=0;
        > int t=wait(&status);
        >}
        >
        >
        >Может быть я что-то упустил - я могу выслать весь код проекта,
        >но вроде всё что надо написал... Если слать код - там
        >примерно 130 Кб неужатого текста, так что не очень много в
        >архиве будет... :-)

        Как я и говорил, при установленном обработчике сигнала SIGCHLD родительскому процессу будет слаться этот сигнал по завершении дочернего процесса. В результате recv получит меньше байт, чем запрошено, если успеет, или вернёт ошибку Interrupted system call - если не успеет ничего получить. Лечится это либо установкой обработчика SIGCHLD в SIG_IGN (если обработчик именно такой, как приведённый выше, то есть делать ничего не надо и он только предотвращает появление зомби) - не уверен в портируемости этого варианта, но под Linux должно работать. Либо изменением класса tcp клиента - чтобы он ловил такие вещи и дочитывал из сокета в случае прерывания по сигналу.
        С connect'ом - тут нету этого класса и кода, где этот объект (c) пытается переконнектиться, так что неизвестно, что он там делает в случае, например, если recv вернёт ошибку. Возможно закрывает сокет. Короче, нужно смотреть этот класс...

        • fork и recv, !*! Serega_S, 16:50 , 18-Окт-05 (4)
          >Как я и говорил, при установленном обработчике сигнала SIGCHLD родительскому процессу будет
          >слаться этот сигнал по завершении дочернего процесса. В результате recv получит
          >меньше байт, чем запрошено, если успеет, или вернёт ошибку Interrupted system
          >call - если не успеет ничего получить. Лечится это либо установкой
          >обработчика SIGCHLD в SIG_IGN (если обработчик именно такой, как приведённый выше,
          >то есть делать ничего не надо и он только предотвращает появление
          >зомби) - не уверен в портируемости этого варианта, но под Linux
          >должно работать. Либо изменением класса tcp клиента - чтобы он ловил
          >такие вещи и дочитывал из сокета в случае прерывания по сигналу.
          >
          >С connect'ом - тут нету этого класса и кода, где этот объект
          >(c) пытается переконнектиться, так что неизвестно, что он там делает в
          >случае, например, если recv вернёт ошибку. Возможно закрывает сокет. Короче, нужно
          >смотреть этот класс...

          Ссори, по части реконнекта это я загнул - он и раньше не работал (хотя должен). Т.е. проблема может быть и не только в сигнале с реконнектом.

          А вот по части recv, проблема в сигнале. Ведь сигналом прерывается выполнение программы, обрабатывается прерывание и управление возвращается обратно в ту же точку программы. Почему же должна прерываться операция считывания/ожидания данных из сокета?

          По части кода:
          Код реконнекта:

          на самом деле коннект происходит так:
          main()
          {
          reconnect:
              do
              {
                  // реконнект
                  if(-1==c.connect())
                  {
                      printf("Не могу присоединиться к серверу");
                      sleep(flags.timeout_reconnect*60);
                  }
                  else
                  {
                      er=(char*)malloc(255);
                      printf("Успешно присоединились к серверу");
                      break;
                  }
              }while(!flags.flag_off);
            // далее читаем данные:
            while(!flags.flag_off)
            {
               t=c.read((char*)&m,sizeof(m));
               if(t==-1)
               {
                   // ошибка
                   goto reconnect;
               }
          и т.д. из предыдущего листинга

          По части класса клиента сокетов:

          int client_tcp::connect(void)
          {
              // прописываем порт и адрес
          #ifdef _WIN32
              if(SOCKET_ERROR==::connect(s,(const struct sockaddr *)&ad,sizeof(ad)))
          #else
              if(-1==::connect(s,(const struct sockaddr *)&ad,sizeof(ad)))
          #endif
              {
                  set_error("connect",errno);
                  return -1;
              }    
              return 0;
          }
          // чтение:
          int client_tcp::read(char* buf, int len)
          {
              int t=len;
              while(t)
              {
                  int m=recv(s,buf,t,0);
          #ifdef _WIN32
                  if(m==SOCKET_ERROR)
          #else
                  if(m==-1)
          #endif
                  {
                      set_error("recv",errno);
                      return -1;
                  }
                  if(m==0)
                  {
                      set_error("'recv' т.к.клиент отсоединился.",errno);
                      return 0;
                  }
                  if(!m)return len-t;
                  t-=m;
                  buf+=m;
              }
              return len;    
          }

          Насчёт обработчика сигнала. Мне нужно избавиться от зомби, чтобы родительский процесс, вызвав kill(SIGUSR1,pid_дитя) понял, что дитя завершило работу и можно освобождать соответствующие данные. Иначе, если дитя стало зомби (жуть какая ;-) ), вызов kill() для дитя не завершается с ошибкой и родительский думает что дитя всё ещё работает. Поэтому обработчик нужен.

          P.S. Там такое взаимодействие, что родитель получив данные, шлёт kill(SIGUSR1) и дитя, получив сигнал, считывает через шаред мемори данные. Т.е. в данной схеме, убив дитя, автоматически убивается зомби и на очередном kill(SIGUSR1) об этом становиться известно родителю.

          • fork и recv, !*! jd, 07:51 , 19-Окт-05 (5)
            >А вот по части recv, проблема в сигнале. Ведь сигналом прерывается выполнение
            >программы, обрабатывается прерывание и управление возвращается обратно в ту же точку
            >программы. Почему же должна прерываться операция считывания/ожидания данных из сокета?

            Уж не помню, почему, но системные вызовы ввода/вывода (типа read/write/recv/send/etc) точно ведут себя именно так. То есть могут вернуть меньше, чем запрошено, если успели что-то получить (в случае с read/recv) или -1 с ошибкой Interrupted system call - если ничего не успели получить к моменту прихода сигнала. Это относится к варианту по-умолчанию. Разумеется их поведение зависит от настроек дескриптора (сокета). Например в блокирующем и неблокирующем режиме оно совершенно разное. Всё это описано в соответствующих манах.

            >По части кода:
            >Код реконнекта:
            >
            >на самом деле коннект происходит так:
            >main()
            >{
            >reconnect:
            > do
            > {
            >  // реконнект
            >  if(-1==c.connect())
            >  {
            >   printf("Не могу присоединиться к серверу");
            >   sleep(flags.timeout_reconnect*60);
            >  }
            >  else
            >  {
            >   er=(char*)malloc(255);
            >   printf("Успешно присоединились к серверу");
            >   break;
            >  }
            > }while(!flags.flag_off);
            >  // далее читаем данные:
            >  while(!flags.flag_off)
            >  {
            >     t=c.read((char*)&m,sizeof(m));
            >     if(t==-1)
            >     {
            >         // ошибка
            >         goto reconnect;
            использовать goto, тем более в C++ - мало какая идея может быть хуже...
            >     }
            > и т.д. из предыдущего листинга
            >
            >По части класса клиента сокетов:
            >
            >int client_tcp::connect(void)
            >{
            > // прописываем порт и адрес
            >#ifdef _WIN32
            > if(SOCKET_ERROR==::connect(s,(const struct sockaddr *)&ad,sizeof(ad)))
            >#else
            > if(-1==::connect(s,(const struct sockaddr *)&ad,sizeof(ad)))
            >#endif
            > {
            >  set_error("connect",errno);
            >  return -1;
            > }
            > return 0;
            >}
            >// чтение:
            >int client_tcp::read(char* buf, int len)
            >{
            > int t=len;
            > while(t)
            > {
            >  int m=recv(s,buf,t,0);
            >#ifdef _WIN32
            >  if(m==SOCKET_ERROR)
            >#else
            >  if(m==-1)
            >#endif
            >  {
            вот здесь нужно вставить дополнительную проверку на EINTR
            >   set_error("recv",errno);
            >   return -1;
            >  }
            >  if(m==0)
            >  {
            >   set_error("'recv' т.к.клиент отсоединился.",errno);
            >   return 0;
            >  }
            >  if(!m)return len-t;
            Это никогда не выполнится, так как при m==0 выйдет двумя строками раньше
            Конечно, вероятно строка осталась от какого-то предыдущего варианта и в данном случае ничего страшного не представляет, но она позволяет предположить, что где-то ещё есть какие-то подобные "хвосты", от которых может уже что-то зависеть

            >  t-=m;
            >  buf+=m;
            > }
            > return len;
            >}
            Вообще, если данные нужны именно все, имеет смысл сделать, чтобы этот метод возвращал bool - либо всё получил, либо что-то произошло (типа разрыва соединения)

            Касательно метода connect() - нужно проследить, где и что ещё делается с сокетом. Возможно он всё-таки где-то закрывается или просто полю s присваивается что-нибудь не то.

            >Насчёт обработчика сигнала. Мне нужно избавиться от зомби, чтобы родительский процесс, вызвав
            >kill(SIGUSR1,pid_дитя) понял, что дитя завершило работу и можно освобождать соответствующие данные.
            >Иначе, если дитя стало зомби (жуть какая ;-) ), вызов kill()
            >для дитя не завершается с ошибкой и родительский думает что дитя
            >всё ещё работает. Поэтому обработчик нужен.

            Как я уже говорил (два раза), чтобы не было зомби, можно просто поставить в качестве обработчика сигнала SIGCHLD специальное значение SIG_IGN. Хотя это работает не на всех платформах. Короче man signal(2). Но коль скоро всё-таки нужно определять, что дочерний процесс завершился, логично было бы выставлять в имеющемся обработчике какой-нибудь флаг, информирующий об этом, вместо того, чтобы слать какие-то сигналы несуществующему процессу.
            Кстати fork может вернуть кроме 0 и pid дочернего процесса ещё и -1.

            P.S. Без обид, но мне кажется, если это всё нормально заработает - это будет чудо.

            • fork и recv, !*! Serega_S, 04:41 , 20-Окт-05 (6)

              >использовать goto, тем более в C++ - мало какая идея может быть
              >хуже...
              В этой ситуации это может быть и лишнее. А вообще бывают ситуации, когда его использование оправдано - например последовательное освобождение ресурсов.

              >Это никогда не выполнится, так как при m==0 выйдет двумя строками раньше
              >
              >Конечно, вероятно строка осталась от какого-то предыдущего варианта и в данном случае
              >ничего страшного не представляет, но она позволяет предположить, что где-то ещё
              >есть какие-то подобные "хвосты", от которых может уже что-то зависеть
              да, старая версия возвращала столько, сколько вернул recv()

              >Но коль скоро
              >всё-таки нужно определять, что дочерний процесс завершился, логично было бы выставлять
              >в имеющемся обработчике какой-нибудь флаг, информирующий об этом, вместо того, чтобы
              >слать какие-то сигналы несуществующему процессу.
              В проге может быть целый ворох потоков и каждый поток запускает свой вьювер через fork, т.е. поток должен знать, что завершился его вьювер. Можно конечно брать pid, но это городить огород из базы pid-ов и т.п. Всё равно в сути обмена между каждым потоком и его вьювером лежит передача вьюверу сигнала, о готовности данных, там в цикле крутиться приём данных и далее передача сигнала вьюверу. Если вьювер убили, то цикл завершается. Т.е. попроще.
              >Кстати fork может вернуть кроме 0 и pid дочернего процесса ещё и
              >-1.
              >
              >P.S. Без обид, но мне кажется, если это всё нормально заработает -
              >это будет чудо.

              Я не волшебник, я только учусь. :-) А вообще работает. Бывают баги, но где их нет...


              • fork и recv, !*! Serega_S, 04:45 , 20-Окт-05 (7)
                >В проге может быть целый ворох потоков и каждый поток запускает свой
                >вьювер через fork, т.е. поток должен знать, что завершился его вьювер.
                >Можно конечно брать pid, но это городить огород из базы pid-ов
                >и т.п. Всё равно в сути обмена между каждым потоком и
                >его вьювером лежит передача вьюверу сигнала, о готовности данных, там в
                >цикле крутиться приём данных и далее передача сигнала вьюверу. Если вьювер
                >убили, то цикл завершается. Т.е. попроще.

                Такой головняк из-за того, что sdl не поддерживает многооконности.

            • fork и recv, !*! Serega_S, 05:18 , 20-Окт-05 (8)
              >Уж не помню, почему, но системные вызовы ввода/вывода (типа read/write/recv/send/etc) точно ведут
              >себя именно так. То есть могут вернуть меньше, чем запрошено, если
              >успели что-то получить (в случае с read/recv) или -1 с ошибкой
              >Interrupted system call - если ничего не успели получить к моменту
              >прихода сигнала. Это относится к варианту по-умолчанию. Разумеется их поведение зависит
              >от настроек дескриптора (сокета). Например в блокирующем и неблокирующем режиме оно
              >совершенно разное. Всё это описано в соответствующих манах.

              Спасибо за помощь! Сделал обработчик EINTR - всё работает правильно.




Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2024 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру