отказ
Мне хорошо известно, что PHP, возможно, не был лучшим выбором в этом случае для сервера сокетов. Пожалуйста, воздержитесь от предложения разных языков / платформ – поверьте мне – я слышал это со всех сторон.
Работая в среде Unix и используя PHP 5.2.17 , моя ситуация такова: я создал сервер сокетов на PHP, который обменивается данными с флеш-клиентами. Мой первый шаг состоял в том, что каждое входящее соединение блокировало последовательные соединения, пока оно не закончило обрабатываться. Я решил это, используя PHP pcntl_fork()
. Я успешно смог создать множество дочерних процессов (сохраняя их PID в родительском), которые занимались передачей сообщений другим клиентам и, следовательно, «освобождали» родительский процесс и позволяли ему продолжать обрабатывать следующее соединение [s].
Моя главная проблема прямо сейчас заключается в обработке / обработке коллекции этих мертвых / зомби-дочерних процессов и их прекращении. Я прочитал (снова и снова) соответствующие страницы руководства PHP для pcntl_fork () и понял, что родительский процесс отвечает за очистку своих детей . Родительский процесс получает SIGNAL из своего дочернего элемента, когда дочерний элемент выполняет exit(0)
. Я могу «поймать» этот сигнал, используя pcntl_signal()
чтобы настроить обработчик сигнала .
Мой signal_handler выглядит так:
declare(ticks = 1); function sig_handler($signo){ global $forks; // this is an array that holds all the child PID's foreach($forks AS $key=>$childPid){ echo "has my child {$childPid} gone away?".PHP_EOL; if (posix_kill($childPid, 9)){ echo "Child {$childPid} has tragically died!".PHP_EOL; unset($forks[$key]); } } }
неdeclare(ticks = 1); function sig_handler($signo){ global $forks; // this is an array that holds all the child PID's foreach($forks AS $key=>$childPid){ echo "has my child {$childPid} gone away?".PHP_EOL; if (posix_kill($childPid, 9)){ echo "Child {$childPid} has tragically died!".PHP_EOL; unset($forks[$key]); } } }
Я действительно вижу, что оба эха включают в себя соответствующий и правильный дочерний PID, который нужно удалить, но кажется, что
posix_kill($childPid, 9)
Я понимаю, что синоним kill -9 $childPid
возвращает TRUE, хотя на самом деле НЕ удаляет этот процесс …
Взято с man-страниц posix_kill
:
Возвращает TRUE при успешном завершении или FALSE при сбое.
Я контролирую дочерние процессы с помощью команды ps
. Они выглядят так:
web5 5296 5234 0 14:51 ? 00:00:00 [php] <defunct> web5 5321 5234 0 14:51 ? 00:00:00 [php] <defunct> web5 5466 5234 0 14:52 ? 00:00:00 [php] <defunct>
Как вы можете видеть, все эти процессы являются дочерними процессами родителя, у которого есть PID 5234
Я что-то пропустил в своем понимании? Кажется, мне удалось заставить все работать (и это так), но я остался с бесчисленными зомби-процессами в системе!
Мои планы на зомби-апокалипсис прочны –
но что я могу сделать, когда даже sudo kill -9
не убивает дочерние процессы зомби?
Я сам ответил на этот вопрос после некоторых дополнительных исследований, если вы все еще можете выдержать мои промахи.
Я обещаю, что в конце есть решение: P
Хорошо … так вот, 10 дней спустя, и я считаю, что я решил эту проблему. Я не хотел добавлять к уже длинному сообщению, поэтому я включу в этот ответ некоторые из вещей, которые я пробовал.
Принимая советы @ sym и читаем больше в документации и комментариях к документации , описание pcntl_waitpid()
гласит:
Если ребенок по запросу pid уже вышел на время вызова (так называемый
Процесс «зомби»), функция немедленно возвращается. Любые системные ресурсы, используемые ребенком
освобождены …
Поэтому я настраиваю свой обработчик pcntl_signal()
следующим образом:
function sig_handler($signo){ global $childProcesses; $pid = pcntl_waitpid(-1, $status, WNOHANG); echo "Sound the alarm! "; if ($pid != 0){ if (posix_kill($pid, 9)){ echo "Child {$pid} has tragically died!".PHP_EOL; unset($childProcesses[$pid]); } } } // These define the signal handling // pcntl_signal(SIGTERM, "sig_handler"); // pcntl_signal(SIGHUP, "sig_handler"); // pcntl_signal(SIGINT, "sig_handler"); pcntl_signal(SIGCHLD, "sig_handler");
сfunction sig_handler($signo){ global $childProcesses; $pid = pcntl_waitpid(-1, $status, WNOHANG); echo "Sound the alarm! "; if ($pid != 0){ if (posix_kill($pid, 9)){ echo "Child {$pid} has tragically died!".PHP_EOL; unset($childProcesses[$pid]); } } } // These define the signal handling // pcntl_signal(SIGTERM, "sig_handler"); // pcntl_signal(SIGHUP, "sig_handler"); // pcntl_signal(SIGINT, "sig_handler"); pcntl_signal(SIGCHLD, "sig_handler");
Для завершения я включу фактический код, который я использую для разветвления дочернего процесса –
function broadcastData($socketArray, $data){ global $db,$childProcesses; $pid = pcntl_fork(); if($pid == -1) { // Something went wrong (handle errors here) // Log error, email the admin, pull emergency stop, etc... echo "Could not fork()!!"; } elseif($pid == 0) { // This part is only executed in the child foreach($socketArray AS $socket) { // There's more happening here but the essence is this socket_write($socket,$msg,strlen($msg)); // TODO : Consider additional forking here for each client. } // This is where the signal is fired exit(0); } // If the child process did not exit above, then this code would be // executed by both parent and child. In my case, the child will // never reach these commands. $childProcesses[] = $pid; // The child process is now occupying the same database // connection as its parent (in my case mysql). We have to // reinitialize the parent's DB connection in order to continue using it. $db = dbEngine::factory(_dbEngine); }
Да … Это соотношение 1: 1 комментариев к коду: P
Так что это выглядело отлично, и я увидел эхо:
Звучит тревога! Ребенок 12345 трагически умер!
Однако, когда петля сервера сокетов сделала следующую итерацию, функция socket_select()
не выполнила эту ошибку:
PHP Предупреждение: socket_select (): невозможно выбрать [4]: Прерванный системный вызов …
Теперь сервер перейдет в вегетативное состояние, совершенно не обращая внимания на окружающий его мир, не реагируя на запросы, отличные от команд ручного уничтожения от корневого терминала.
Я не собираюсь рассказывать, почему это происходит или что я сделал после этого, чтобы отладить его … давайте просто скажем, что это была разочаровывающая неделя …
много кофе, боль глаз и 10 дней спустя …
Бросьте барабан, пожалуйста
Упомянутый здесь комментарий 2007 года в документации по php- сокетам и в этом руководстве по stuporglue (поиск «хорошего воспитания») можно просто «игнорировать» сигналы, поступающие от дочерних процессов ( SIGCHLD
), передавая SIG_IGN
в pcntl_signal()
функция –
pcntl_signal(SIGCHLD, SIG_IGN);
Цитата из этого связанного сообщения в блоге:
Если мы игнорируем SIGCHLD, дочерние процессы будут получены автоматически после завершения.
Верьте или нет – я включил эту pcntl_signal()
, удалил всех других обработчиков и все, что pcntl_signal()
детей, и это сработало! Больше не осталось <defunct>
процессов, которые висели вокруг!
В моем случае мне действительно неинтересно, что я точно знал, когда умер ребенок, или кто это был, меня это совсем не интересовало – просто они не повесились и не разбили весь мой сервер: P
Относительно вашего заявления об отказе от ответственности – PHP не лучше / хуже, чем многие другие языки для написания сервера. Есть некоторые вещи, которые невозможно сделать (легкие процессы, асинхронные операции ввода-вывода), но они действительно не применяются к серверу forking. Если вы используете OO-код, убедитесь, что у вас включена циклическая проверка ссылок на сборщик мусора.
Когда дочерний процесс завершается, он становится зомби, пока родительский процесс не очистит его. Ваш код, кажется, посылает сигнал KILL каждому ребенку при получении любого сигнала. Он не будет очищать записи процесса. Он завершит процессы, которые не вызвали выход. Чтобы правильно обработать дочерний процесс, вы должны вызвать waitpid (см. Также этот пример на странице руководства pcntl_wait).
http://www.linuxsa.org.au/tips/zombies.html
Зомби – мертвые процессы. Вы не можете убить мертвых. Все процессы в конечном счете умирают, и когда они делают, они становятся зомби. Они не потребляют почти никаких ресурсов, чего можно ожидать, потому что они мертвы! Причина зомби в том, что родитель (процесс) зомби может получить статус выхода зомби и статистику использования ресурсов. Родитель сигнализирует операционной системе, что он больше не нуждается в зомби, используя один из системных вызовов wait ().
Когда процесс умирает, его дочерние процессы становятся детьми процесса номер 1, который является процессом init. Init – это «всегда», ожидая, что дети умрут, чтобы они не оставались зомби.
Если у вас есть процессы зомби, это означает, что их зомби не ждали их родители (посмотрите на PPID, отображаемый ps -l). У вас есть три варианта: исправить родительский процесс (заставить его подождать); убить родителя; или жить с ним. Помните, что жить с ним не так сложно, потому что зомби занимают не более одной дополнительной строки на выходе ps.
Я слишком хорошо знаю, как трудно найти решение проблемы зомби-процессов. Моя забота о том, чтобы потенциально иметь сотни или тысячи из них (правильно или ошибочно, поскольку я не знаю, действительно ли это было бы проблемой), заканчивается из inodes, так как все ад может сломаться, когда это произойдет.
Если бы только страница pcntl_fork (), связанная с posix-setsid (), многие из нас обнаружили бы, что решение было так просто много лет назад.