09 fév. 2009
Transformer un script PHP en Daemon ...
Par Christophe de saint leger le lundi, février 9 2009, 09:32 - PHP - Lien permanent
Nous allons voir ici comment faire pour qu'un script PHP puisse devenir un "Daemon" indépendant du navigateur web , personnellement je l'utilise par exemple dans un de mes projets en tant que partie serveur , le script et lancé au démarrage du serveur , puis écoute sur un port donné , pour effectuer une tache précise à la connexion d'un client ...
A vous de trouver l'utilisation adéquate d'un tel système ..
Pré-requis :
Pour créer un daemon , nous allons devoir contrôler les processus via la bibliothèque PCNTL de php , pour l'installation , il faut juste compiler PHP avec l'option --enable-pcntl , et surtout , ne pas mettre --disable-cli ni --disable-cgi , car le script qui utilise les fonctions PCNTL , doivent être lancé en CGI , ou CLI , comme expliqué dans la documentation , il vous sera donc impossible de lancer votre script à partir d'un navigateur , sauf si vous utiliser php en mode CGI/FastCGI et non , en module apache .
Le principe :
Le principe est le suivant , l'utilisateur avec pouvoir ( ici root ) lance le script principale , qui dans un premier temps "fork" le processus courant , ( il crée donc un fils identique à lui-même ) , puis le processus pére se tue "Kill" . Le fils passe en chef d'orchestre change son processus en chef de session Le fils va ensuite changer d'utilisateur afin de s'exécuter sur un utilisateur avec le minimum de droits possible ( ici ce sera nobody ) . Dans un troisième temps , le fils va déclarer ses propres méthodes à la réception d'un signal du système . Et enfin , le script peut se lancer sans problème .., indépendamment du processus pére ou du terminal qui à lancer le script ..
L'exemple :
Comme expliqué plus haut , nous allons reprendre chaque étape importante et voir le code "PHP" correspondant ... aller c'est parti ...
Forker le processus pére :
$pid = pcntl_fork();
if ($pid == -1)
{
/* Échec du fork*/
echo "Échec du fork!\n";
exit();
}elseif ($pid)
{
/* Termine le process père */
exit();
}else
{
/* Process Fils*/
//Fait du processus courant un chef de session
posix_setsid();
//Changement du répertoire courant pour l'exécution du script
chdir('/');
//change le umask de PHP en 0777
umask(0);
//Retourne l'identifiant du processus fils
return posix_getpid();
}
Voilà comment creer un démon .. dans le script ci-dessus , c'est la fonction pcntl_fork() qui s'occupe de dupliquer les processus , ensuite , vous imaginez deux processus qui s'exécutent en parallèle .. l'un qui a un $pid ( le pére , car c'est la pére qui a lancé le fork ) et un autre qui n'en a pas ( le fils , car $pid n'est pas défini ) . En tue donc le pére avec la commande exit() . Le processus fils continue donc seul son chemin .. il fait du processus courant un chef de session ... via la fonction posix_setsid(); , pour ensuite changer le répertoire courant , et le mask de php , pour au finale ,retourner son identifiant .
Changer d'utilisateur
Pour des raison de sécurité , il faut changer d'utilisateur courant , car là nous somme toujours en root :
if( !posix_setgid( 65534 ) )
{
print "Erreur setgid :" . $gid . "!\n";
exit;
}
if( !posix_setuid( 65534 ) )
{
print "Erreur setuid :" . $uid . "!\n";
exit;
}
Voilà , pour le changement d'utilisateur , nous avons récupéré l'UID et le GID de l'utilisateur nobody pour l'attribuer au processus fils via les fonctions posix_setuid() et posix_setgid()
Les signaux :
Le script fils peut déclarer ses propres méthodes pour gérer comme il l'entend ( ou plutôt comme VOUS l'entendez ) , la réaction aux signaux systéme .
declare(ticks = 1);
/* handle signals */
pcntl_signal(SIGTERM, 'sig_handler');
pcntl_signal(SIGINT, 'sig_handler');
pcntl_signal(SIGCHLD, 'sig_handler');
/**
* Signal handler
*/
function sig_handler($sig)
{
switch($sig)
{
case SIGTERM:
case SIGINT:
exit();
break;
case SIGCHLD:
pcntl_waitpid(-1, $status);
break;
}
}
La gestion des signaux ci-dessus , utilise les ticks déprécié depuis php 5.3 ... il faudra donc trouver une autre alternative ... ici , la gestion des signaux n'est pas obligatoire !
Lancement :
Pour lancer votre daemon, plusieurs solutions , soit apache fonctionne avec php en mode CGI/FastCGI , alors , vous pouvez lancer le daemon via votre navigateur ( mais mauvaise idée , car de ce fait , votre navigateur doit se trouver sur la même machine que votre script et être lancer en root ), sinon, ce sera en ligne de commande , comme ceci :
En root:
php /dir/de/votre/script/serveur.php
Le script se lance puis s'arrête aussitôt .. car en faite , le script que vous lancez est le process pére .. qui dés qu'il a créé son fils , se tue !
Pour vérifier que votre daemon se trouve bien dans la liste des processus , il vous suffit de le rechercher dans les process courant , exemple pour serveur.php :
ps -xa | grep serveur.php
Qui retourne une ligne de ce style :
25951 ? Ss 0:00 php /dir/du/script/serveur.php
Voilà , en espérant avoir écrit un script intéressant , sans trop entrer dans les détails ..
Christophe.

Commentaires
La fonction stream_socket_server() est pas mal sinon, pour gérer des cas simples sans faire de multithreading.
le problème avec ce genre d'astuce, c'est que PHP occupe petit à petit de plus en plus de mémoire dans la RAM....
Comment gérer vous la durée de vie maximum de votre démon? Comment faites vous face à l'explosion de l'espace mémoire??
J'avoue ne pas avoir tester assez de temps pour remarquer une eventuelle fuite mémoire ..
Qu'est qui peut provoquer cette fuite ? ( les scripts d'attente de connexions ? ).
Je teste un script qui utilise ces fonctions , il fonctionne non-stop depuis jeudi midi 05-02-2009 .. pour le moment pas d'explosion d'occupation de mémoire .
Si c'était le cas , je pense qu'il faut tuer le process pour en créer un nouveau toutes les X secondes par exemple ..
dans ce cas , la fonction pcntl_alarm peut nous être utile .. il y a peut être d'autres solutions , point à éclaicir donc ...
Merci de votre participation .
Ch.
Je viens de comprendre le problème que tu voulais souligner , sur l'occupation de la mémoire d'un démon ..
en lisant cet article :
http://www.unixgarden.com/index.php...
Qui explique aussi comment mettre en place un daemon , mais de façon plus pointue et en langage C .. ce qui est vraiment différent de php .
L'avantage de PHP dans ce cas là , est que tout ce qui est mémoire , est géré par l'interpreteur Zend, c'est lui qui alloue et libère la mémoire nécessaire au fonctionnement du script.
Donc , normalement , pas de fuite mémoire ...
Bonjour,
Je confirme que des problèmes de fuites mémoires sont rencontrés lorsque l'on fait une utilisation cyclique sur des scripts PHP. Simplement parce PHP avant la version 5.3 ne dispose pas de "garbage collector". Même si vous utilisez des fonctions comme unset() vous pourrez observer que la mémoire n'est pas libérée.
Pour les versions antérieurs à PHP 5.3 la meilleur solution est encore d'utiliser la fonction respawn de Linux. ( si le script tombe alors il est relancé ) en le couplant avec un timer qui stopperai le deamon toutes les 10 mns par exemple.
Avec l'arrivée de PHP 5.3 vous pouvez utiliser les fonctions de collecte ( gc_ ) il n'y a donc plus de problèmes de références cycliques.
Cordialement.
@Alexis gruet : Merci pour ce retour et ces informations .
cdt,
Ch.
Salut !
Trés bon article, merci.
En ce qui concerne le code déprécié des ticks en v5.3, la solution consiste à utiliser les fermetures et fonctions lambda. voilà bon ocurage !
Merci pour ces infos ,
Je vais creuser ce point ... d'après la doc les ticks ne sont plus dépréciés ?
Ch.
Pour information, j'ai des démons PHP qui tourne en permanence sur ma machine de développement pour piloter mon window manager (wmii, pour ne pas le nommer, et oui je sais, je suis dingue).
Je n'ai jamais observé la moindre fuite de mémoire au niveau de PHP (j'avoue en avoir été le premier surpris, néanmoins), et cela même avec des versions antérieures à 5.3.
J'aurais donc tendance à dire que tout cela tient plus de la légende urbaine qu'autre chose.
Par contre, j'attire votre attention sur le fait que forker php via pcntl_fork() avec mod_php sous apache revient à forker... apache.
Et ca, c'est une TRES mauvaise idée.
Les démons en PHP, ca peut être utile, mais avant tout en CLI.
Merci pour ce retour d'exp , et cette info très percutante sur le fork .
Christophe.
Et non , ce n'est pas une légende urbain ... malheureusement .
J'utilise cette fonction pcntl_fork avec une version antérieur à php 5.3 avec 6 threads par rapport au processus père . Je suis en train de me casser la tête avec ces problème de fuites mémoire. malrgé les unset et autre optimisation , les fuites sont bien là et fait planter mon daemon.
Merci pour ce retour .
Et bonne chance pour trouver une solution ...
N'hésite pas à l'éditer ici si tu trouves
Ch.