Ну начнем с того, что довольно часто приходится сталкиваться с тем, что необходимо реализовывать какую-либо серверную часть для обработки каких-то данных и т.д. Естественно, что сервеную часть удобней всего было бы реализовать в виде демона. В свое время я наткнулся на подобный класс реализации демонов написанного на Python. И вот на прошлой неделе решил написать такое же творение на PHP, вроде получилось не плохо, оценивать Вам.
Итак, начнем с того, что все исходники лежат на bitbucket.org и GitHub (кому как удобней), документация там тоже написана. Собственно вот код самого класса:
Сразу извинюсь за то, что код не слишком подробно откомментирован, обещаю исправить. Пока что написал только секции phpdoc. Для реализации своего демона нужно наследоваться от класса DaemonPHP и реализовать абстрактный метод run(), в котором и будет код вашего демона:
Рассмотрим описанный выше код. Мы создали класс MyDaemon, который наследует абстрактный класс DaemonPHP. Все методы в классе DaemonPHP объявлены, как final, кроме одного — это абстрактный метод run(). В тело этого метода помещается код, который должен выполнять Ваш демон. В нашем случае это просто пустой бесконечный цикл, чтобы увидеть работу демона. Далее мы создали объект $daemon класса MyDaemon, в конструктор передается абсолютный путь, где будет создан PID-файл демона, если не передать этот параметр, то по-умолчанию PID-файл будет создан в том же каталоге, где лежит файл демона с именем daemon-php.pid. Далее мы устанавливаем директорию для выполнения chroot методом setChroot(), это было добавлено сразу же из соображений безопасности, но делать это не обязательн��. Кстати, для выполнения chroot может потребоваться запуск демона от root'а. Далее указываются абсолютные пути для LOG-файла и ERR-файла, если эти параметры не указаны, то будут созданы файлы daemon-php.log и daemon-php.err в текущей директории. В дальнейшем я думаю расширить конструктор, чтобы все эти опции можно было передавать сразу в конструктор. Далее мы вызываем метод handle(), в который передаем аргументы командной строки $argv. Этот метод сделан специально для того, чтобы Вы не думали о создании конструкции switch-case для обработки аргументов командной строки. Но, тем не менее вы можете не вызывать этот метод, а сделать что-то свое, у класса есть публичные методы start(), stop(), restart(), status(). Названия методов говорят сами за себя, собственно эти же аргументы ожидает получить handle().
Обращу ваше внимание на то, что в текущей директории может появится файл php_error.log, он появляется только тогда, когда возникают ошибки в самом PHP и пишет лог этих ошибок в него.
Сохраняем файл с кодом, например под именем run.php и запускаем:
Статус проверить можно соответствующей командой:
Ну и соответственно останавливаем демон:
Самый свежий код этого класса всегда доступен на репозитории (ссылка была выше). Ну вот и все, жду Ваших комментариев и советов по доработке и дополнению функционала.
Итак, начнем с того, что все исходники лежат на bitbucket.org и GitHub (кому как удобней), документация там тоже написана. Собственно вот код самого класса:
<?php /* Author: Petr Bondarenko E-mail: public@shamanis.com Date: 31 May 2012 License: BSD Description: Class for create UNIX-daemon */ class DaemonException extends Exception {} abstract class DaemonPHP { protected $_baseDir; protected $_chrootDir = null; protected $_pid; protected $_log; protected $_err; /** * Конструктор класса. Принимает путь к pid-файлу * @param string $path Абсолютный путь к PID-файлу */ public function __construct($path=null) { $this->_baseDir = dirname(__FILE__); $this->_log = $this->_baseDir . '/daemon-php.log'; $this->_err = $this->_baseDir . '/daemon-php.err'; if ($path === null) { $this->_pid = $this->_baseDir . '/daemon-php.pid'; } else { $this->_pid = $path; } } /** * Метод устанавливает путь log-файла * @param string $path Абсолютный путь к log-файлу * @return DaemonPHP */ final public function setLog($path) { $this->_log = $path; return $this; } /** * Метод устанавливает путь err-файла * @param string $path Абсолютный путь к err-файлу * @return DaemonPHP */ final public function setErr($path) { $this->_err = $path; return $this; } /** * Метод позволяет установить директорию, * в которую будет выполнен chroot после старта демона. * Данный метод служит для решения проблем безопасности. * @param string $path Абсолютный путь chroot-директории */ final public function setChroot($path) { if (!function_exists('chroot')) { throw new DaemonException('Function chroot() has no. Please update you PHP version.'); } $this->_chrootDir = $path; return $this; } /** * Метод выполняет демонизацию процесса, через double fork */ final protected function demonize() { $pid = pcntl_fork(); if ($pid == -1) { throw new DaemonException('Not fork process!'); } else if ($pid) { exit(0); } posix_setsid(); chdir('/'); $pid = pcntl_fork(); if ($pid == -1) { throw new DaemonException('Not double fork process!'); } else if ($pid) { $fpid = fopen($this->_pid, 'wb'); fwrite($fpid, $pid); fclose($fpid); exit(0); } posix_setsid(); chdir('/'); ini_set('error_log', $this->_baseDir . '/php_error.log'); fclose(STDIN); fclose(STDOUT); fclose(STDERR); $STDIN = fopen('/dev/null', 'r'); if ($this->_chrootDir !== null) { chroot($this->_chrootDir); } $STDOUT = fopen($this->_log, 'ab'); if (!is_writable($this->_log)) throw new DaemonException('LOG-file is not writable!'); $STDERR = fopen($this->_err, 'ab'); if (!is_writable($this->_err)) throw new DaemonException('ERR-file is not writable!'); $this->run(); } /** * Метод возвращает PID процесса * @return int PID процесса */ final protected function getPID() { if (file_exists($this->_pid)) { $pid = (int) file_get_contents($this->_pid); if (posix_kill($pid, SIG_DFL)) { return $pid; } else { //Если демон не откликается, а PID-файл существует unlink($this->_pid); return 0; } } else { return 0; } } /** * Метод стартует работу и вызывает метод demonize() */ final public function start() { if (($pid = $this->getPID()) > 0) { echo "Process is running on PID: " . $pid . PHP_EOL; } else { echo "Starting..." . PHP_EOL; $this->demonize(); } } /** * Метод останавливает демон */ final public function stop() { if (($pid = $this->getPID()) > 0) { echo "Stopping ... "; posix_kill($pid, SIGTERM); unlink($this->_pid); echo "OK" . PHP_EOL; } else { echo "Process not running!" . PHP_EOL; } } /** * Метод рестартует демон последовательно вызвав stop() и start() */ final public function restart() { $this->stop(); $this->start(); } /** * Метод проверяет работу демона */ final public function status() { if (($pid = $this->getPID()) > 0) { echo "Process is running on PID: " . $pid . PHP_EOL; } else { echo "Process not running!" . PHP_EOL; } } /** * Метод обрабатывает аргументы командной строки */ final public function handle($argv) { switch ($argv[1]) { case 'start': $this->start(); break; case 'stop': $this->stop(); break; case 'restart': $this->restart(); break; case 'status': $this->status(); break; default: echo "Unknown command!" . PHP_EOL . "Use: " . $argv[0] . " start|stop|restart|status" . PHP_EOL; break; } } /** * Основной класс демона, в котором выполняется работа. * Его необходимо переопределить */ abstract public function run(); } ?>
Сразу извинюсь за то, что код не слишком подробно откомментирован, обещаю исправить. Пока что написал только секции phpdoc. Для реализации своего демона нужно наследоваться от класса DaemonPHP и реализовать абстрактный метод run(), в котором и будет код вашего демона:
<?php require_once 'daemon.php'; class MyDaemon extends DaemonPHP { public function run() { while (true) { } } } $daemon = new MyDaemon('/tmp/test.pid'); $daemon->setChroot('/home/shaman/work/PHPTest/daemon') //Устанавливаем каталог для chroot ->setLog('/my.log') ->setErr('/my.err') //После chroot файлы будут созданы в /home/shaman/work/PHPTest/daemon ->handle($argv); } ?>
Рассмотрим описанный выше код. Мы создали класс MyDaemon, который наследует абстрактный класс DaemonPHP. Все методы в классе DaemonPHP объявлены, как final, кроме одного — это абстрактный метод run(). В тело этого метода помещается код, который должен выполнять Ваш демон. В нашем случае это просто пустой бесконечный цикл, чтобы увидеть работу демона. Далее мы создали объект $daemon класса MyDaemon, в конструктор передается абсолютный путь, где будет создан PID-файл демона, если не передать этот параметр, то по-умолчанию PID-файл будет создан в том же каталоге, где лежит файл демона с именем daemon-php.pid. Далее мы устанавливаем директорию для выполнения chroot методом setChroot(), это было добавлено сразу же из соображений безопасности, но делать это не обязательн��. Кстати, для выполнения chroot может потребоваться запуск демона от root'а. Далее указываются абсолютные пути для LOG-файла и ERR-файла, если эти параметры не указаны, то будут созданы файлы daemon-php.log и daemon-php.err в текущей директории. В дальнейшем я думаю расширить конструктор, чтобы все эти опции можно было передавать сразу в конструктор. Далее мы вызываем метод handle(), в который передаем аргументы командной строки $argv. Этот метод сделан специально для того, чтобы Вы не думали о создании конструкции switch-case для обработки аргументов командной строки. Но, тем не менее вы можете не вызывать этот метод, а сделать что-то свое, у класса есть публичные методы start(), stop(), restart(), status(). Названия методов говорят сами за себя, собственно эти же аргументы ожидает получить handle().
Обращу ваше внимание на то, что в текущей директории может появится файл php_error.log, он появляется только тогда, когда возникают ошибки в самом PHP и пишет лог этих ошибок в него.
Сохраняем файл с кодом, например под именем run.php и запускаем:
user@localhost:~$ php run.php start
Starting...
user@localhost:~$Статус проверить можно соответствующей командой:
user@localhost:~$ php run.php status
Process is running on PID: 6539Ну и соответственно останавливаем демон:
user@localhost:~$ php run.php stop
Stopping ... OKСамый свежий код этого класса всегда доступен на репозитории (ссылка была выше). Ну вот и все, жду Ваших комментариев и советов по доработке и дополнению функционала.
