Зачем может понадобиться писать демоны на PHP?
Если у тебя возникнет вопрос по поводу какой-то незнакомой функции — не расстраивайся! Они все задокументированы в PHP Manual. Вряд ли у меня получится рассказать о них подробнее и интереснее.
Как из одного процесса сделать два? Программистам под Windows (в том числе и мне) больше знакома система, когда мы пишем функцию, которая будет
Итак. Есть такая функия
После
Фишка в том, что
Кстати,
Чтобы демонизировать скрипт, нужно отвязать его от консоли и пустить в бесконечный цикл. Давай посмотрим, как это делается.
После таких действий мы остаемся с демоном — программой без консоли. Чтобы она не завершила свое выполнение немедленно, пускаем ее в бесконечный цикл (ну, почти):
На данный момент наш демон однопроцессовый. По ряду очевидных причин этого может быть недостаточно. Рассмотрим создание дочерних процессов.
Следующая по важности задача — обеспечение обработки сигналов. Сейчас наш демон ничего не знает о внешнем мире, и убить его можно только завершением процесса через
Есть куча интересных сигналов, которые можно обрабатывать, но мы остановимся на
Вот и все. Мы перехватываем сигнал — ставим флаг в скрипте — используем этот флаг, чтоб не запускать новые потоки и завершить основной цикл.
И последний штрих. Нужно, чтобы демон не запускался два раза. Обычно для этих целей используются т.н. .pid-файлы — файл, в котором записан pid данного конкретного демона, если он запущен.
А после демонизации — нужно записать в pid-файл текущий
Вот и все, что нужно знать для написания демонов на PHP. Я не рассказывал об общем доступе к ресурсам, потому что эта проблема шире, чем написание демонов.
Удачи!
Статья с подсветкой синтаксиса — на моем блоге.
- Выполнение трудоемких фоновых задач;
- выполнение задач, которые длятся больше, чем время ожидания при HTTP-запросе (30 секунд);
- выполнение задач на более высоком уровне доступа, чем серверный процесс (читай — под рутом).
Основы
- PID — идентификатор процесса. Уникальное для текущего момента положительное число.
- pcntl — расширение PHP для работы с дочерними процессами. Курим мануал.
- posix — расширение PHP для работы с функциями стандарта POSIX. Курим мануал.
Если у тебя возникнет вопрос по поводу какой-то незнакомой функции — не расстраивайся! Они все задокументированы в PHP Manual. Вряд ли у меня получится рассказать о них подробнее и интереснее.
Форкинг (плодим процессы)
Как из одного процесса сделать два? Программистам под Windows (в том числе и мне) больше знакома система, когда мы пишем функцию, которая будет
main()
для дочернего потока. В *nix все не так, потому я немного расскажу об этой системе многопроцессовости. *nixоиды могут смело пропустить эту часть, если они и так все знают.Итак. Есть такая функия
pcntl_fork
. Как ни странно, аргументов она не берет. Что же делать?После
pcntl_fork
у скрипта начинается шизофрения: код вроде бы один и тот же, но выполняется двумя параллельными процессами. Впрочем, если просто вставить в скрипт pcntl_fork
, ничего наглядного ты не увидишь, разве что конфликты доступа к ресурсам.Фишка в том, что
pcntl_fork
возвращает 0 дочернему процессу и PID дочернего процесса — родительскому. Вот обычный паттерн использования pcntl_fork
:$pid = pcntl_fork(); if ($pid == -1) { //ошибка } elseif ($pid) { //сюда попадет родительский процесс } else { //а сюда - дочерний процесс } //а сюда попадут оба процесса
Кстати,
pcntl_fork
работает только в CGI и CLI-режимах. Из-под апача — нельзя. Логично.Демонизация
Чтобы демонизировать скрипт, нужно отвязать его от консоли и пустить в бесконечный цикл. Давай посмотрим, как это делается.
// создаем дочерний процесс $child_pid = pcntl_fork(); if( $child_pid ) { // выходим из родительского, привязанного к консоли, процесса exit; } // делаем основным процессом дочерний. // После этого он тоже может плодить детей. // Суровая жизнь у этих процессов... posix_setsid();
После таких действий мы остаемся с демоном — программой без консоли. Чтобы она не завершила свое выполнение немедленно, пускаем ее в бесконечный цикл (ну, почти):
while (!$stop_server) { //TODO: делаем что-то }
Дочерние процессы
На данный момент наш демон однопроцессовый. По ряду очевидных причин этого может быть недостаточно. Рассмотрим создание дочерних процессов.
$child_processes = array(); while (!$stop_server) { if (!$stop_server and (count($child_processes) < MAX_CHILD_PROCESSES)) { //TODO: получаем задачу //плодим дочерний процесс $pid = pcntl_fork(); if ($pid == -1) { //TODO: ошибка - не смогли создать процесс } elseif ($pid) { //процесс создан $child_processes[$pid] = true; } else { $pid = getmypid(); //TODO: дочерний процесс - тут рабочая нагрузка exit; } } else { //чтоб не гонять цикл вхолостую sleep(SOME_DELAY); } //проверяем, умер ли один из детей while ($signaled_pid = pcntl_waitpid(-1, $status, WNOHANG)) { if ($signaled_pid == -1) { //детей не осталось $child_processes = array(); break; } else { unset($child_processes[$signaled_pid]); } } }
Обработка сигналов
Следующая по важности задача — обеспечение обработки сигналов. Сейчас наш демон ничего не знает о внешнем мире, и убить его можно только завершением процесса через
kill -SIGKILL
. Это плохо. Это очень плохо — SIGKILL
прервет процессы на середине. Кроме того, ему никак нельзя передать информацию.Есть куча интересных сигналов, которые можно обрабатывать, но мы остановимся на
SIGTERM
— сигнале корретного завершения работы.//Без этой директивы PHP не будет перехватывать сигналы declare(ticks=1); //Обработчик function sigHandler($signo) { global $stop_server; switch($signo) { case SIGTERM: { $stop_server = true; break; } default: { //все остальные сигналы } } } //регистрируем обработчик pcntl_signal(SIGTERM, "sig_handler");
Вот и все. Мы перехватываем сигнал — ставим флаг в скрипте — используем этот флаг, чтоб не запускать новые потоки и завершить основной цикл.
Поддержание уникальности демона
И последний штрих. Нужно, чтобы демон не запускался два раза. Обычно для этих целей используются т.н. .pid-файлы — файл, в котором записан pid данного конкретного демона, если он запущен.
function isDaemonActive($pid_file) { if( is_file($pid_file) ) { $pid = file_get_contents($pid_file); //проверяем на наличие процесса if(posix_kill($pid,0)) { //демон уже запущен return true; } else { //pid-файл есть, но процесса нет if(!unlink($pid_file)) { //не могу уничтожить pid-файл. ошибка exit(-1); } } } return false; } if (isDaemonActive('/tmp/my_pid_file.pid')) { echo 'Daemon already active'; exit; }
А после демонизации — нужно записать в pid-файл текущий
PID
демона. file_put_contents('/tmp/my_pid_file.pid', getmypid());
Вот и все, что нужно знать для написания демонов на PHP. Я не рассказывал об общем доступе к ресурсам, потому что эта проблема шире, чем написание демонов.
Удачи!
Статья с подсветкой синтаксиса — на моем блоге.