Ну начнем с того, что довольно часто приходится сталкиваться с тем, что необходимо реализовывать какую-либо серверную часть для обработки каких-то данных и т.д. Естественно, что сервеную часть удобней всего было бы реализовать в виде демона. В свое время я наткнулся на подобный класс реализации демонов написанного на 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Самый свежий код этого класса всегда доступен на репозитории (ссылка была выше). Ну вот и все, жду Ваших комментариев и советов по доработке и дополнению функционала.
