Программирование сокетов: accept() задерживается

У меня есть сервер, написанный на C, который заблокирован в функции accept() и ожидает новых входящих подключений. Когда новое соединение принято, он создает новый процесс, вызывая fork(). Я не использую epoll, поскольку каждый клиентский сокет обрабатывается независимым процессом, а одна из используемых им библиотек дает сбой в многопоточной среде.

Вот код сервера:

srv_sock = init_unix_socket();
listen(srv_sock, 5);
/* Other code which handles SIGCLD. */
while (1) {
    log_info("Awaiting new incoming connection.");
    clt_sock = accept(srv_sock, NULL, NULL);
    if (clt_sock < 0) {
        log_err("Error ...");
        continue;
    }
    log_info("Connection %d accepted.", clt_sock);

    cld_pid = fork();
    if (cld_pid < 0) {
        log_err("Failed to create new process.");
        close(clt_sock);
        continue;
    }
    if (clt_pid == 0) {
        /* Initialize libraries. */
        /* Handle client connection ...  */
        shutdown(clt_sock, SHUT_RDWR);
        close(clt_sock);
        _exit(0);
    }
    else {
        log_info("Child process created for socket %d.", clt_sock);
        close(clt_sock);
    }
}

Клиент написан на Java, он подключается к серверу с помощью библиотеки junixsocket, так как Java не поддерживает сокет домена Unix. Когда он подключается к серверу, он отправляет запрос (заголовок + XML-документ) и ожидает ответа от сервера.

Вот код клиента:

File socketFile = new File(UNIX_SOCKET_PATH);
AFUNIXSocket socket = AFUNIXSocket.newInstance();
socket.connect(new AFUNIXSocketAddress(socketFile));

InputStream sis = socket.getInputStream();
OutputStream sos = socket.getOutputStream();
logger.info("Connected with server.");

byte[] requestHeader;
byte[] requestBuffer;

sos.write(requestHeader, 0, requestHeader.length);
logger.info("Header sent.");

sos.write(requestBuffer, 0, requestBuffer.length);
logger.info("Request XML sent.");

sos.flush();

Теперь проблема в том, что у меня есть 3 клиентских потока, которые одновременно подключаются к серверу. У меня всегда запущена одна задача, а две другие ждут завершения первой.

Я проверил журналы. Все 3 клиентских потока подключились и отправили запрос на сервер (почти) в одно и то же время, но сервер принял только первый поток и задержал 2 других. Согласно журналам, существует задержка в 3 минуты между connect на стороне клиента и accept на стороне сервера.

Сначала я подумал, что задержка может быть вызвана каким-то буфером, поэтому я вызываю OutputStream.flush() после каждого вызова OutputStream.write, но проблема остается.

Я не могу понять, что может вызвать эту задержку, есть идеи, пожалуйста?

Спасибо.

Обновление от 15 марта 2016 г.

pstack показывает, что родительский процесс был заблокирован в waitpid в моем обработчике SIGCHLD. Вероятно, поэтому accept не возвращался, когда поступало новое входящее соединение, поскольку процедура выполнения была прервана обработчиком сигнала.

Вот код моего обработчика сигналов:

static void _zombie_reaper (int signum) {
    int status;
    pid_t child;

    if (signum != SIGCHLD) {
        return;
    }
    while ((child = waitpid(-1, &status, WNOHANG)) != -1) {
        continue;
    }
}

/* In main function */
struct sigaction sig_act;
memset(&sig_act, 0, sizeof(struct sigaction));
sigemptyset(&sig_act.sa_mask);
sig_act.sa_flags = SA_NOCLDSTOP;
sig_act.sa_handler = _zombie_reaper;
if (sigaction(SIGCHLD, &sig_act, NULL) < 0) {
    log_err("Failed to register signal handler.");
}

person vesontio    schedule 15.03.2016    source источник
comment
А где код серверной части C? Это был бы первый подозреваемый в такой проблеме. Довольно сложно решить эту проблему вообще без кода.   -  person Antti Haapala    schedule 15.03.2016
comment
Извините, @AnttiHaapala, я добавил код сервера.   -  person vesontio    schedule 15.03.2016
comment
Проголосовал. Пока не вижу ничего плохого в коде сервера :( Как выводится лог, есть ли задержка в 3 минуты между Awaiting new incoming connection. и Connection accepted? Возможно это на стороне клиента то   -  person Antti Haapala    schedule 15.03.2016
comment
Минуты - это долго. Похоже, ваш сервер отказывается от соединения, и клиенты снова подключаются через минуту. Проверьте аргумент «незавершенная работа» для «прослушивания» на вашем сервере и попробуйте увеличить значение. Попробуем 64.   -  person Marian    schedule 15.03.2016
comment
Не уверен насчет Linux, но на MAC-странице man для fork есть зловещее предупреждение: Существуют ограничения на то, что вы можете делать в дочернем процессе. Чтобы быть в полной безопасности, вы должны ограничить себя выполнением только безопасных операций с асинхронным сигналом до тех пор, пока не будет вызвана одна из функций exec. Все API, включая глобальные символы данных, в любой структуре или библиотеке следует считать небезопасными после fork(), если только явно не задокументировано, что они безопасны или безопасны для асинхронных сигналов.   -  person user3386109    schedule 15.03.2016
comment
@user3386109 user3386109 обычно проблема заключается в смешивании fork()ing с многопоточностью; если вы используете чужие фреймворки, у вас могут быть потоки, не зная об этом   -  person Antti Haapala    schedule 15.03.2016
comment
@ user3386109 может быть прав. Вы закрываете сокет srv_sock в дочернем процессе?   -  person Marian    schedule 15.03.2016
comment
Есть ли причина, по которой вы используете fork? Если вы заботитесь о производительности, запуск отдельного потока (не говоря уже о процессе) для каждого сокета заставит вас плакать, когда вы масштабируете до 10000 соединений (и, следовательно, 10000 потоков/процессов/что угодно). Рассмотрите возможность использования неблокирующих или асинхронных вызовов сокетов или установки неблокирующих параметров сокета (что отличается от использования неблокирующих или асинхронных вызовов сокетов). Вы должны быть в состоянии создать достаточно производительное серверное приложение всего за один поток. Если это не работает, вы можете масштабировать это, используя pthread_create, не fork.   -  person autistic    schedule 15.03.2016
comment
@Seb, это сказано в вопросе. Форк необходим для правильной работы связанной библиотеки. одна из библиотек, которые он использует, дает сбой в многопоточной среде.   -  person Antti Haapala    schedule 15.03.2016
comment
@vesontio, какая у нас операционная система?   -  person Antti Haapala    schedule 15.03.2016
comment
@AnttiHaapala Вы прочитали остальную часть моего сообщения? Почему нельзя использовать неблокирующие сокеты? Вы не ОП, поэтому я не ожидаю, что вы сможете ответить на этот вопрос... но если бы вы были ОП и ответили так, я бы объяснил, что вы тратим время на проблему XY. Не спрашивайте о вашем очевидном решении проблемы сбоя. Спросите о крушении. Это могут быть признаки более зловещих вещей, которые fork не может решить.   -  person autistic    schedule 15.03.2016
comment
Привет @AnttiHaapala, я добавил Java-код клиента. Я просто сравнил логи сервера и клиента. Между Connection accepted и Connected with server есть задержка в 3 минуты. Обе программы работают на одном компьютере, поэтому они используют одно и то же системное время.   -  person vesontio    schedule 15.03.2016
comment
Привет @Marian, спасибо за ваше предложение, мой код теперь использует backlog размера 5, я буду тестировать с большим значением. И... я никогда не закрываю srv_sock в дочернем процессе. У меня есть обработчик сигналов, который wait для завершенного дочернего процесса, и я использовал только функции, безопасные для асинхронных сигналов, в обработчике сигналов.   -  person vesontio    schedule 15.03.2016
comment
@user3386109 user3386109 Я думал, что async-signal-safe предназначен для обработчика сигналов.   -  person vesontio    schedule 15.03.2016
comment
Привет @Seb, это то, что я сделал в своей первой версии, epoll + pthread и т. д., и каждая задача обрабатывается независимым потоком. Однако библиотека, которую я должен использовать, время от времени страдает от сбоя, когда одна задача вызывает сбой библиотеки, вся программа не работает, как и все другие параллельные задачи. Вот почему я должен создать эту самую многопроцессорную версию.   -  person vesontio    schedule 15.03.2016
comment
@AnttiHaapala Я использую Centos 6.6.   -  person vesontio    schedule 15.03.2016
comment
Попробуйте закрыть srv_sock в дочернем процессе сразу после форка.   -  person Marian    schedule 15.03.2016
comment
@vesontio Почему вам нужно, чтобы каждую задачу обрабатывал независимый поток? Потоки — это оптимизация... Есть ли у вас доказательства того, что однопоточная версия слишком медленная? Проблема с вашим подходом заключается в том, что вы не знаете, насколько быстрой/медленной является однопоточная версия, поэтому вы не можете сравнить ее с вашей распараллеленной версией. Вы догадываетесь об оптимизации и, скорее всего, ошибаетесь. Как только вы закончите однопоточную версию, используйте профилировщик, чтобы определить наиболее существенное узкое место, и поработайте над его оптимизацией (сначала без потоков; они в крайнем случае).   -  person autistic    schedule 15.03.2016
comment
Ох, но многопоточность будет быстрее - очень неудачный и дезинформированный, но типичный ответ на мою линию вопросов ... Имеет смысл, что процессор с 8 ядрами может выполнять только 8 задач одновременно; если вы запускаете более 8 потоков, вы можете тратить переключатели контекста впустую. Знаете ли вы, что проблема C10K (10000 FTP-клиентов на одном сервере) была решена с помощью одного нить, еще в 1999 году? Конечно, с этим пониманием вы могли бы понять, почему мне любопытно, что вы не пробовали однопоточную версию...   -  person autistic    schedule 15.03.2016
comment
1. Ваш дочерний процесс должен делать close(srv_sock) , так как у дочернего процесса нет причин использовать этот сокет - это может помочь выявить другие ошибки, которые могут возникнуть, если, например. ошибка приводит к тому, что дочерний процесс начинает использовать неправильный файловый дескриптор. То же самое с другими файловыми дескрипторами, которые дочерний процесс не должен использовать, закройте их.   -  person nos    schedule 15.03.2016
comment
@nos, спасибо, я сделаю это.   -  person vesontio    schedule 15.03.2016


Ответы (1)


Ваше условие waitpid() неверно, вы хотите продолжать вызывать waitpid() только в том случае, если он собрал дочерний процесс, поэтому вам нужно сделать

while ((child = waitpid(-1, &status, WNOHANG)) > 0) {
     continue;
 }
person nos    schedule 15.03.2016