Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

> Demon UNIXowy w php, Czyli słow kilka o egzorcyzmach ;D
Seth
post
Post #1





Grupa: Przyjaciele php.pl
Postów: 2 335
Pomógł: 6
Dołączył: 7.03.2002

Ostrzeżenie: (0%)
-----


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);
}
 }
}
?>
pobierz, plaintext
  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.

Ten post edytował Seth 7.02.2006, 17:01:42
Go to the top of the page
+Quote Post
 
Start new topic
Odpowiedzi
Seth
post
Post #2





Grupa: Przyjaciele php.pl
Postów: 2 335
Pomógł: 6
Dołączył: 7.03.2002

Ostrzeżenie: (0%)
-----


Jak najbardziej mogl by dzialac w petli (zreszta i tak i tak dziala (IMG:http://forum.php.pl/style_emoticons/default/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 (IMG:http://forum.php.pl/style_emoticons/default/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 (IMG:http://forum.php.pl/style_emoticons/default/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. ?>


Ten post edytował Seth 8.02.2006, 13:11:51
Go to the top of the page
+Quote Post

Posty w temacie
- Seth   Demon UNIXowy w php   7.02.2006, 16:53:58
- - ActivePlayer   ja odnosnie etapów. Cytat    * Rozwidlic...   7.02.2006, 18:45:54
- - Seth   Jak najbardziej mogl by dzialac w petli (zreszta i...   7.02.2006, 21:10:49
- - LBO   hmmm.. a moze kilka słów o zastosowaniach... ?.. ...   23.02.2006, 17:03:56
- - slash.   co do tematu demona w srodowisku windows, cytujac ...   25.02.2006, 12:22:49

« Następny starszy · Archiwum Pro · Następny nowszy »
 

Reply to this topicStart new topic
2 Użytkowników czyta ten temat (2 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Aktualny czas: 27.12.2025 - 02:51