Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Demon UNIXowy w php
Forum PHP.pl > Forum > PHP > Pro > Archiwum Pro
Seth
Pod wplywem tematu 1);
define('DLOG_NOTICE'2);
define('DLOG_WARNING'4);
define('DLOG_ERROR'8);
define('DLOG_CRITICAL'16);

/**
 * Daemon base class
 *
 * Requirements:
 * Unix like operating system
 * php 4 >= 4.3.0 or php 5
 * php compiled with:
 * --enable-sigchild
 * --enable-pcntl
 *
 * @package binarychoice.system.unix
 * @author Michal 'Seth' Golebiowski <seth at binarychoice dot pl>
 * @copyright Copyright 2005 Seth
 * @since 1.0.3
 */
class Daemon
{
 
/**#@+
* @access public
*/
 /**
* User ID

* @var int
* @since 1.0
*/
 
var $userID 99;

 
/**
* Group ID

* @var integer
* @since 1.0
*/
 
var $groupID 99;
 
 
/**
* Terminate daemon when set identity failure ?

* @var bool
* @since 1.0.3
*/
 
var $requireSetIdentity false;

 
/**
* Path to PID file

* @var string
* @since 1.0.1
*/
 
var $pidFileLocation '/tmp/daemon.pid';

 
/**
* Home path

* @var string
* @since 1.0
*/
 
var $homePath '/';
 
/**#@-*/


 /**#@+
* @access protected
*/
 /**
* Current process ID

* @var int
* @since 1.0
*/
 
var $_pid 0;

 
/**
* Is this process a children

* @var boolean
* @since 1.0
*/
 
var $_isChildren false;

 
/**
* Is daemon running

* @var boolean
* @since 1.0
*/
 
var $_isRunning false;
 
/**#@-*/


 /**
* Constructor
*
* @access public
* @since 1.0
* @return void
*/
 
function Daemon()
 {
error_reporting(0);
set_time_limit(0);
ob_implicit_flush();

register_shutdown_function(array(&$this'releaseDaemon'));
 }

 
/**
* Starts daemon
*
* @access public
* @since 1.0
* @return bool
*/
 
function start()
 {
$this->_logMessage('Starting daemon');

if (!
$this->_daemonize())
{
 
$this->_logMessage('Could not start daemon'DLOG_ERROR);

 return 
false;
}


$this->_logMessage('Running...');

$this->_isRunning true;


while (
$this->_isRunning)
{
 
$this->_doTask();
}

return 
true;
 }

 
/**
* Stops daemon
*
* @access public
* @since 1.0
* @return void
*/
 
function stop()
 {
$this->_logMessage('Stoping daemon');

$this->_isRunning false;
 }

 
/**
* Do task
*
* @access protected
* @since 1.0
* @return void
*/
 
function _doTask()
 {
// override this method
 
}

 
/**
* Logs message
*
* @access protected
* @since 1.0
* @return void
*/
 
function _logMessage($msg$level DLOG_NOTICE)
 {
 
// override this method
 
}

 
/**
* Daemonize
*
* Several rules or characteristics that most daemons possess:
* 1) Check is daemon already running
* 2) Fork child process
* 3) Sets identity
* 4) Make current process a session laeder
* 5) Write process ID to file
* 6) Change home path
* 7) umask(0)

* @access private
* @since 1.0
* @return void
*/
 
function _daemonize()
 {
ob_end_flush();

if (
$this->_isDaemonRunning())
{
 
// Deamon is already running. Exiting
 
return false;
}

if (!
$this->_fork())
{
 
// Coudn't fork. Exiting.
 
return false;
}

if (!
$this->_setIdentity() && $this->requireSetIdentity)
{
 
// Required identity set failed. Exiting
 
return false;
}

if (!
posix_setsid())
{
 
$this->_logMessage('Could not make the current process a session leader'DLOG_ERROR);

 return 
false;
}

if (!
$fp = @fopen($this->pidFileLocation'w'))
{
 
$this->_logMessage('Could not write to PID file'DLOG_ERROR);

 return 
false;
}
else
{
 
fputs($fp$this->_pid);
 
fclose($fp);
}

@
chdir($this->homePath);
umask(0);

declare(
ticks 1);

pcntl_signal(SIGCHLD, array(&$this'sigHandler'));
pcntl_signal(SIGTERM, array(&$this'sigHandler'));

return 
true;
 }

 
/**
* Cheks is daemon already running
*
* @access private
* @since 1.0.3
* @return bool
*/
 
function _isDaemonRunning()
 {
$oldPid = @file_get_contents($this->pidFileLocation);

if (
$oldPid !== false && posix_kill(trim($oldPid),0))
{
 
$this->_logMessage('Daemon already running with PID: '.$oldPid, (DLOG_TO_CONSOLE DLOG_ERROR));

 return 
true;
}
else
{
 return 
false;
}
 }

 
/**
* Forks process
*
* @access private
* @since 1.0
* @return bool
*/
 
function _fork()
 {
$this->_logMessage('Forking...');

$pid pcntl_fork();

if (
$pid == -1// error
{
 
$this->_logMessage('Could not fork'DLOG_ERROR);

 return 
false;
}
else if (
$pid// parent
{
 
$this->_logMessage('Killing parent');

 exit();
}
else 
// children
{
 
$this->_isChildren true;
 
$this->_pid posix_getpid();

 return 
true;
}
 }

 
/**
* Sets identity of a daemon and returns result
*
* @access private
* @since 1.0
* @return bool
*/
 
function _setIdentity()
 {
if (!
posix_setgid($this->groupID) || !posix_setuid($this->userID))
{
 
$this->_logMessage('Could not set identity'DLOG_WARNING);

 return 
false;
}
else
{
 return 
true;
}
 }

 
/**
* Signals handler
*
* @access public
* @since 1.0
* @return void
*/
 
function sigHandler($sigNo)
 {
switch (
$sigNo)
{
 case 
SIGTERM:  // Shutdown
$this->_logMessage('Shutdown signal');
exit();
break;

 case 
SIGCHLD:  // Halt
$this->_logMessage('Halt signal');
while (
pcntl_waitpid(-1$statusWNOHANG) > 0);
break;
}
 }

 
/**
* Releases daemon pid file
* This method is called on exit (destructor like)
*
* @access public
* @since 1.0
* @return void
*/
 
function releaseDaemon()
 {
if (
$this->_isChildren && file_exists($this->pidFileLocation))
{
 
$this->_logMessage('Releasing daemon');

 
unlink($this->pidFileLocation);
}
 }
}
?>
O_CONSOLE'1);
define('DLOG_NOTICE'2);
define('DLOG_WARNING'4);
define('DLOG_ERROR'8);
define('DLOG_CRITICAL'16);

/**
 * Daemon base class
 *
 * Requirements:
 * Unix like operating system
 * php 4 >= 4.3.0 or php 5
 * php compiled with:
 * --enable-sigchild
 * --enable-pcntl
 *
 * @package binarychoice.system.unix
 * @author Michal 'Seth' Golebiowski <seth at binarychoice dot pl>
 * @copyright Copyright 2005 Seth
 * @since 1.0.3
 */
class Daemon
{
 
/**#@+
* @access public
*/
 /**
* User ID

* @var int
* @since 1.0
*/
 
var $userID 99;

 
/**
* Group ID

* @var integer
* @since 1.0
*/
 
var $groupID 99;
 
 
/**
* Terminate daemon when set identity failure ?

* @var bool
* @since 1.0.3
*/
 
var $requireSetIdentity false;

 
/**
* Path to PID file

* @var string
* @since 1.0.1
*/
 
var $pidFileLocation '/tmp/daemon.pid';

 
/**
* Home path

* @var string
* @since 1.0
*/
 
var $homePath '/';
 
/**#@-*/


 /**#@+
* @access protected
*/
 /**
* Current process ID

* @var int
* @since 1.0
*/
 
var $_pid 0;

 
/**
* Is this process a children

* @var boolean
* @since 1.0
*/
 
var $_isChildren false;

 
/**
* Is daemon running

* @var boolean
* @since 1.0
*/
 
var $_isRunning false;
 
/**#@-*/


 /**
* Constructor
*
* @access public
* @since 1.0
* @return void
*/
 
function Daemon()
 {
error_reporting(0);
set_time_limit(0);
ob_implicit_flush();

register_shutdown_function(array(&$this'releaseDaemon'));
 }

 
/**
* Starts daemon
*
* @access public
* @since 1.0
* @return bool
*/
 
function start()
 {
$this->_logMessage('Starting daemon');

if (!
$this->_daemonize())
{
 
$this->_logMessage('Could not start daemon'DLOG_ERROR);

 return 
false;
}


$this->_logMessage('Running...');

$this->_isRunning true;


while (
$this->_isRunning)
{
 
$this->_doTask();
}

return 
true;
 }

 
/**
* Stops daemon
*
* @access public
* @since 1.0
* @return void
*/
 
function stop()
 {
$this->_logMessage('Stoping daemon');

$this->_isRunning false;
 }

 
/**
* Do task
*
* @access protected
* @since 1.0
* @return void
*/
 
function _doTask()
 {
// override this method
 
}

 
/**
* Logs message
*
* @access protected
* @since 1.0
* @return void
*/
 
function _logMessage($msg$level DLOG_NOTICE)
 {
 
// override this method
 
}

 
/**
* Daemonize
*
* Several rules or characteristics that most daemons possess:
* 1) Check is daemon already running
* 2) Fork child process
* 3) Sets identity
* 4) Make current process a session laeder
* 5) Write process ID to file
* 6) Change home path
* 7) umask(0)

* @access private
* @since 1.0
* @return void
*/
 
function _daemonize()
 {
ob_end_flush();

if (
$this->_isDaemonRunning())
{
 
// Deamon is already running. Exiting
 
return false;
}

if (!
$this->_fork())
{
 
// Coudn't fork. Exiting.
 
return false;
}

if (!
$this->_setIdentity() && $this->requireSetIdentity)
{
 
// Required identity set failed. Exiting
 
return false;
}

if (!
posix_setsid())
{
 
$this->_logMessage('Could not make the current process a session leader'DLOG_ERROR);

 return 
false;
}

if (!
$fp = @fopen($this->pidFileLocation'w'))
{
 
$this->_logMessage('Could not write to PID file'DLOG_ERROR);

 return 
false;
}
else
{
 
fputs($fp$this->_pid);
 
fclose($fp);
}

@
chdir($this->homePath);
umask(0);

declare(
ticks 1);

pcntl_signal(SIGCHLD, array(&$this'sigHandler'));
pcntl_signal(SIGTERM, array(&$this'sigHandler'));

return 
true;
 }

 
/**
* Cheks is daemon already running
*
* @access private
* @since 1.0.3
* @return bool
*/
 
function _isDaemonRunning()
 {
$oldPid = @file_get_contents($this->pidFileLocation);

if (
$oldPid !== false && posix_kill(trim($oldPid),0))
{
 
$this->_logMessage('Daemon already running with PID: '.$oldPid, (DLOG_TO_CONSOLE DLOG_ERROR));

 return 
true;
}
else
{
 return 
false;
}
 }

 
/**
* Forks process
*
* @access private
* @since 1.0
* @return bool
*/
 
function _fork()
 {
$this->_logMessage('Forking...');

$pid pcntl_fork();

if (
$pid == -1// error
{
 
$this->_logMessage('Could not fork'DLOG_ERROR);

 return 
false;
}
else if (
$pid// parent
{
 
$this->_logMessage('Killing parent');

 exit();
}
else 
// children
{
 
$this->_isChildren true;
 
$this->_pid posix_getpid();

 return 
true;
}
 }

 
/**
* Sets identity of a daemon and returns result
*
* @access private
* @since 1.0
* @return bool
*/
 
function _setIdentity()
 {
if (!
posix_setgid($this->groupID) || !posix_setuid($this->userID))
{
 
$this->_logMessage('Could not set identity'DLOG_WARNING);

 return 
false;
}
else
{
 return 
true;
}
 }

 
/**
* Signals handler
*
* @access public
* @since 1.0
* @return void
*/
 
function sigHandler($sigNo)
 {
switch (
$sigNo)
{
 case 
SIGTERM:  // Shutdown
$this->_logMessage('Shutdown signal');
exit();
break;

 case 
SIGCHLD:  // Halt
$this->_logMessage('Halt signal');
while (
pcntl_waitpid(-1$statusWNOHANG) > 0);
break;
}
 }

 
/**
* Releases daemon pid file
* This method is called on exit (destructor like)
*
* @access public
* @since 1.0
* @return void
*/
 
function releaseDaemon()
 {
if (
$this->_isChildren && file_exists($this->pidFileLocation))
{
 
$this->_logMessage('Releasing daemon');

 
unlink($this->pidFileLocation);
}
 }
}
?>
  1. <?php
  2. class TestDaemon extends Daemon
  3. {
  4.  function TestDaemon()
  5.  {
  6. parent::Daemon();
  7.  
  8. $fp = fopen('/tmp/daemon.log', 'a');
  9. fclose($fp);
  10.  
  11. chmod('/tmp/daemon.log', 0777);
  12.  }
  13.  
  14.  function _logMessage($msg, $status = DLOG_NOTICE)
  15.  {
  16. if ($status & DLOG_TO_CONSOLE)
  17. {
  18.  print $msg."\n";
  19. }
  20.  
  21. $fp = fopen('/tmp/daemon.log', 'a');
  22. fwrite($fp, date("Y/m/d H:i:s ").$msg."\n");
  23. fclose($fp);
  24.  }
  25.  
  26.  function _doTask()
  27.  {
  28. static $i = 0;
  29.  
  30. sleep(1);
  31.  
  32. $i++;
  33.  
  34. if ($i >= 30)
  35. {
  36.  $this->stop();
  37. }
  38.  }
  39.  
  40. }
  41. ?>

Klasa TestDaemon rozszeza podstawowa klase Daemon i nadpisuje dwie metody: _doTask() i _logMessage().

_doTask() - jest caly czas uruchamiana w petli przez demona.
W przykladzie TestDaemon w metodzie tej utworzylem zmienna statyczna $i, ktora nie zmienia swojej wartosci po wyjsciu z funkcji.
sleep(1) posluzyl mi do opoznienia dzialania znajdujacego sie dalej kodu o jedna sekunde.
Jezeli nie ustawimy chociaz usleep(100) [albo mniej] - czyli 1 dziesiatej sekundy - wtedy zuzycie procesora bedzie bliskie 99% gdyz demon caly czas wykonuje jedna funkcje w petli, a ta moze sie wykonac tysiace razy w ciagu jednej sekundy, w zaleznosci od szybkosci sprzetu.
Jako, ze nie potrzebujemy tylu wywolan tej funkcji podczas jednej sekundy dajemy opoznienie.
W dalszej czesci metody widac, ze do momentu az $i nie osiagnie 30 dalej pozwalamy demonowi dzialac.
W efekcie po 30 sekundach zostanie zamkniety daemon.

_logMessage() - sluzy nam do logowania zdarzen klasy Deamon


Potrzebny bedzie nam jeszcze jeden plik, ktory odpali nam caly przyklad:
  1. <?php
  2. require_once ('Daemon.class.php');
  3. require_once ('TestDaemon.class.php');
  4.  
  5. $Daemon = new TestDaemon();
  6. $Daemon->start();
  7. ?>

Zauwazcie, ze po jego uruchomieniu odrazu wrocimy do obszaru wpisywania polecen w konsoli. Tak sie dzieje gdyz podczas rozdzielenia procesu na dwa (dwa identyczne rozniace sie numerkami procesu), nastepuje zamkniecie procesu rodzica - czyli tego, ktory zostal uruchomiony spod konsoli - a proces dziecka, nadal dziala w tle.

Caly proces dzialania mozemy przesledzic czytajac logi demona.
Najlatwiej bedzie nam na bierzaco odczytywac plik daemon.log przy uzyciu komendy tail w wierszu polecen:
Kod
tail -f /tmp/daemon.log



I to wzasadzie tyle. Teraz tylko trzeba wymyslic cos ciekawego i zaprzasc demona do pracy. Mysle, ze mozna by nawet pokusic sie o napisanie wlasnego serwera WWW w php wlasnie w oparciu o ta klase.
ActivePlayer
ja odnosnie etapów.
Cytat
    * Rozwidlic procesy - utworzyc proces dziecka (fork), a proces rodzica zamknac. A przed tym zamknac jakiekolwiek polaczenia do pliku gdyz moze to prowadzic co nadpisania danych procesu rodzica lub dziecka.

Nigdy nie zajmowalem sie tym wczesniej, wiec zastanawiam sie po co ? demon nie moglby dzialac w petli i poprostu sprawdzac czy jest cos 'todo' ? no chyba ze to jest jakas forma zapewnienia wielowątkowosci? dlaczego wiec zamykac proces rodzica?

Co do wykozystania... pierwsze na mysl przyszlo mi 'cron', na podstawie tej klasy mozna by napisac alternatywe crona. drugie o czym pomyslalem to implementacja pod windowsami.
Seth
Jak najbardziej mogl by dzialac w petli (zreszta i tak i tak dziala winksmiley.jpg) ale tworzenie kopi rodzica ma nieco inny sens. Otoz jedna kopia (rodzic) jest jakby przypisana do konsoli - dopoki ona dziala nie moze "uwolnic" konsoli. Tworzac jej kopie (dziecko) automatycznie przechodzi ona w tlo i nie ma powiazania z konsola (nie mozna nic wypisac z poziomu dzicka na konsole).
Teraz zamykamy proces rodzica, przez co odlaczamy sie od konsoli i mozemy - my jako user - korzystac z konsoli.
Tymczasem nadal dziala sobie kopia (dziecko) naszego demona - w tle.

Na liscie procesow wygladalo by to mniej wiecej tak:

1) Start programu
Kod
Nr. PID           Nazwa
21432             Demon


2) Rozdzielamy procesy (fork)
Kod
Nr. PID          Nazwa
21432            Demon
|
  \---- 21438     Demon(dziecko)


3) Zamykamy rodzica
Kod
Nr. PID          Nazwa
|
  \---- 21438     Demon(dziecko)


4) Teraz musimy ustawic dziecko na session leadera
Kod
Nr. PID          Nazwa
21438            Demon(dziecko)


Teraz co do konsoli: w pkt. 2 Demon z PIDem 21432 ma dostep do konsoli, a 21438 juz nie. W tym momencie mamy zajeta konsole i nie mozemy wykonywac zadnych polecen.
W pkt 3 mamy juz zwolniona konsole i mozemy z niej korzystac a nadal mamy dzialajacego demona w tle.

Do tego mozemy kontrolowac demona za pomoca sygnalow np:
Kod
kill SIGTERM [nr pid]



--------------------------------------------------[ edit ]

Co do implementacji pod Windowsa to niestety nie jest to mozliwe uzywajac standardowych rozszezen php. Mozna by napisac handlera serwisu Windowsowego w innym jezyku, ktory by korzystal z php ale jest to za duzo zachodu.

Jest jednak nadzieja biggrin.gif Dzisiaj znalazlem cos co moze sprawic, ze nawet pod Windowsa bedziemy mogli napisac swoj serwis (demona).
Rozszezenie win32service napisany przez Weza Furlonga, ktore znajduje sie w zbiorze rozszezen PECL php:
http://snaps.php.net/win32/PECL_5_0/
Dziala co prawda tylko pod php 5 ale raczej nie bedzie to wielkim problemem winksmiley.jpg

Patrzac na przyklad uzycia tego rozszezenia mozna smialo powiedziec, ze jest bardzo latwe w uzyciu:
  1. <?php
  2. /* A sample service:
  3.  *
  4.  * php sample.php install
  5.  * net start dummyphp
  6.  * net stop dummyphp
  7.  * php sample.php uninstall
  8.  */
  9.  
  10. if ($argv[1] == 'install') {
  11. $x = win32_create_service(array(
  12. 'service' => 'dummyphp',
  13. 'display' => 'sample dummy php service',
  14. 'params' => __FILE__ . ' run',
  15. ));
  16. } else if ($argv[1] == 'uninstall') {
  17. $x = win32_delete_service('dummyphp');
  18. } else if ($argv[1] != 'run') {
  19. die("bogus args");
  20. }
  21.  
  22. $x = win32_start_service_ctrl_dispatcher('dummyphp');
  23.  
  24. while (WIN32_SERVICE_CONTROL_STOP != win32_get_last_control_message()) {
  25. usleep(250000);
  26. }
  27.  
  28. ?>
LBO
hmmm.. a moze kilka słów o zastosowaniach... questionmark.gif?..

bardzo mi sie podoba pomysl by uzyc takiego demona jako XMLsocket dla Flasha.. zreszta, cos takiego juz podobno fukcjonuje i to na polskim rynku... kto bywa na http://flashzone.pl i zna Wizzarda ten wie o czym mowie
slash.
co do tematu demona w srodowisku windows, cytujac manula php:
Cytat
As of php 5, a new php-win.exe file is distributed. This is equal to the CLI version, except that php-win doesn't output anything and thus provides no console (no "dos box" appears on the screen). This behavior is similar to php-gtk. You should configure with --enable-cli-win32.


http://pl2.php.net/features.commandline

Niestety nie testowalem tego do konca, i na chwile obecna nie jestem w stanie powiedziec czy jest mozliwosc uruchomienia tego w obrebie innego uzytkownika aby zrobic z tego faktycznego demona, a wlasciwie usluge, ktora pozostanie aktywan po wylogowaniu usera.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2024 Invision Power Services, Inc.