Преобразование в многопоточное сокетное приложение - PullRequest
1 голос
/ 11 января 2009

Поскольку я сейчас выполняю этот проект только на C, я до этого момента только использовал свой веб-сервер в качестве однопоточного приложения. Однако я не хочу этого больше! Итак, у меня есть следующий код, который обрабатывает мою работу.

void BeginListen()
{
        CreateSocket();

        BindSocket();

        ListenOnSocket();

        while ( 1 )
        {
            ProcessConnections();
        }
}

Теперь я добавил fork(); перед началом ProcessConnection();, что помогает мне разрешить несколько подключений! Однако, когда я добавляю код для демона приложения, найденного в , этот ответ . Я включил небольшую проблему, используя fork() создаст копию всего моего работающего приложения, которое является целью fork(). Итак, я бы хотел решить эту проблему.

Мой ProcessConnection() выглядит так

void ProcessConnections()
{
        fork();

        addr_size = sizeof(connector);

        connecting_socket = accept(current_socket, (struct sockaddr *)&connector, &addr_size);

        if ( connecting_socket < 0 )
        {
                perror("Accepting sockets");
                exit(-1);
        }

        HandleCurrentConnection(connecting_socket);


        DisposeCurrentConnection();
}

Как бы я сделал, просто добавив пару строк выше или после connecting=socket = accept ..., чтобы заставить его принимать более одного соединения одновременно? Могу ли я использовать fork();, но когда дело доходит до DisposeCurrentConnection();, я хочу убить этот процесс и просто запустить родительский поток.

Ответы [ 5 ]

4 голосов
/ 11 января 2009

Я не на 100% уверен в том, что вы пытаетесь сделать, покупая мне на макушку, я бы предпочел сделать форк после принятия, и просто выйти (), когда вы ' сделано. Имейте в виду, однако, что вам нужно реагировать на сигнал SIGCHLD при выходе из дочернего процесса, иначе у вас будет куча зомби-процессов, ожидающих передачи своего состояния выхода родительскому процессу. C-псевдо-код:

for (;;) {
  connecting_socket = accept(server_socket);
  if (connecting_socket < 0)
    {
      if (errno == EINTR)
        continue;
      else
        {
          // handle error
          break;
        }
    }

  if (! (child_pid = fork ()))
    {
       // child process, do work with connecting socket
       exit (0);
    }
  else if (child_pid > 0)
    {
      // parent process, keep track of child_pid if necessary.
    }
  else
    {
      // fork failed, unable to service request, send 503 or equivalent.
    }
}

child_pid необходим (как уже упоминалось) для уничтожения дочернего процесса, а также, если вы хотите использовать waitpid для получения статуса выхода.

Что касается зомби-процессов, если вы не заинтересованы в том, что случилось с процессом, вы можете установить механизм обработки сигналов для SIGCHLD и просто зациклить на waitpid с -1, пока не останется дочерних процессов, как это

while (-1 != waitpid (-1, NULL, WNOHANG))
  /* no loop body */ ;

Функция waitpid будет возвращать pid вышедшего потомка, поэтому, если вы хотите, вы можете соотнести это с некоторой другой информацией о соединении (если вы отслеживали pid). Имейте в виду, что accept, вероятно, завершится с errno, установленным в EINTR, без действительного соединения, если перехвачен SIGCHLD, поэтому не забудьте проверить это при приеме return.

EDIT:
Не забудьте проверить наличие ошибок, т. Е. Fork возвращает -1.

2 голосов
/ 11 января 2009

Говорить о fork () и потоках в unix не совсем правильно. Fork создает совершенно новый процесс, у которого нет общего адресного пространства с родительским.

Я думаю, что вы пытаетесь создать модель «процесс-на-запрос», очень похожую на традиционный веб-сервер Unix, такой как NCSA httpd или Apache 1.x, или, возможно, создать многопоточный сервер с общей глобальной памятью:

Серверы обработки запросов:

Когда вы вызываете fork (), система создает клон родительского процесса, включая дескрипторы файлов. Это означает, что вы можете принять запрос сокета и затем выполнить форк. Дочерний процесс имеет запрос сокета, на который он может ответить, а затем завершить.

Это относительно эффективно в Unix, поскольку память процесса физически не копируется - страницы распределяются между процессом. Система использует механизм, называемый копирование при записи, чтобы делать копии постранично, когда дочерний процесс выполняет запись в память. Таким образом, издержки сервера процесса на запрос в Unix не так велики, и многие системы используют эту архитектуру.

1 голос
/ 16 ноября 2010

Лучше использовать функцию select (), которая позволяет слушать и подключаться с разных Запросы в одной программе за копию программы, которая приводит к неэффективности памяти ....

select(Max_descr, read_set, write_set, exception_set, time_out);

т.е. ты можешь

fd_set* time_out;
fd_set* read_set;
listen(1);
listen(2);
while(1)
{
  if(select(20, read_set, NULL,NULL, timeout) >0)
  {
    accept(1);
    accept(2); .....
    pthread_create(func);....
  }
  else
}
0 голосов
/ 11 января 2009

Согласно моему комментарию, этот сервер на самом деле не многопоточный, а многопроцессный.

Если вы хотите простой способ заставить его принимать несколько подключений (и вам не слишком важны показатели производительности), вы можете заставить его работать с inetd . Это оставляет работу по порождению процессов и превращению в демон для inetd, и вам просто нужно написать программу, которая обрабатывает и обрабатывает одно соединение. edit: или, если это упражнение для вас, вы можете взять источник inetd и посмотреть, как он это делает

Вы также можете делать то, что хотите, без потоков или новых процессов, используя select.

Вот статья , в которой объясняется, как использовать select (довольно низкие накладные расходы по сравнению с веткой или потоками - вот пример облегченного веб-сервера, написанного таким образом)

Также, если вы не готовы делать это в C, и C ++ в порядке, вы можете подумать о переносе кода для использования ACE . Это также хорошее место для поиска шаблонов проектирования того, как это сделать, поскольку я считаю, что он поддерживает практически любую модель обработки соединений и очень переносим.

0 голосов
/ 11 января 2009

Проверьте возвращаемое значение fork (). Если он равен нулю, вы - дочерний процесс, и вы можете выйти () после выполнения вашей работы. Если это положительное число, то это идентификатор процесса вновь созданного процесса. Это может позволить вам убить () дочерние процессы, если по какой-то причине они слишком долго остаются без дела.

...