#!/usr/bin/perl -w -I/usr/lib/perl #****************************************** # script serveur.pl - Serveur TCP/IP # Usage : serveur.pl /dir/bases.rrd # # Ce programme met à jour le contenu de bases rrdtool et mysql # suite à la reception de données envoyées par client.pl # # CARACTERISTIQUES de fonctionnement : # - preforkage d'un nbre défini de fils # - chaque fils meurt après N clients # - ecoute le port TCP nomme TCPReport dans /etc/services (6970 par ex.) # - traite 2 type de trames : # -> HOSTNAME_CLIENT rrd:BASE EPOCH:VAL1:VAL2:... pour insertion dans une base RRDTool # -> HOSTNAME_CLIENT sql:BASE:TABLE VAL1:VAL2:... pour insertion dans une base MySQL # # Maxence - Version 1.0 du 9 septembre 2003 # ***************************************** use POSIX; use IO::Socket; use RRDs; # utilisation des packages de gestion RRDTool use DBI; # utilisation des packages de gestion MySQL # variables globales my $PREFORK = 3; # Nbre de fils en attente de connexion cliente my $NB_MAX_CLIENTS_PAR_FILS = 30; # Nbre de connexions traitees par un fils avant qu'il ne meurt my %processus_fils = (); # tableau des PID des fils créés my $processus_fils = 0; # Nbre de processus fils créés my $rrdpath='/appli/rrdtool/bases/postfix/'; # chemin des bases RRDTool qui sont mises à jour sub wlog($$) # on journalise le premier parametre dans /var/log/nom_du_prog.log avec la date d'inscription { my $data=shift; my $quit=shift; # quitte le programme si $quit = 1 my $prog = $0; open(LOG, ">> /var/log/$prog.log") or die("Ouverture de /var/log/$data.log impossible : $!"); my ($jour, $mois, $an, $heure, $min, $sec )=(localtime)[3,4,5,2,1,0]; my $date=sprintf "%02d/%02d/%d %02d:%02d:%02d",$jour,($mois+1),($an+1900),$heure,$min,$sec; print LOG "$date $data\n"; close(LOG); if ($quit == 1) { exit(0); } } sub traitement($) # Traitement des donnees recues sur la socket { my $data=shift; # recupération des donnees reçues chop($data); # suppression du caractère fin de ligne my @decoup=split(/ /, $data); # decoupage de la donnee (2 types) : # -> $localname rrd:$localname_msg.rrd:0 EPOCH:val1:val2:... # -> $localname sql:$base:$table val1,val2,val3,... @middle=split(/:/, $decoup[1]); # decoupage de la partie centrale (cf ci-dessus) # TRAITEMENT DE LA DONNEE SELON LE TYPE #************************************************************************ #***** Cette partie désactive la suite ; elle permet de tester la ******* #***** réception des donnees sans mettre en oeuvre MySQL/RRDTool ******* #************************************************************************ wlog("J'ai recu : $data",0); exit(0); #********************* FIN DU TEST ************************************** if ($middle[0] eq 'rrd') # data pour base RRDTool { RRDs::update("$rrdpath$middle[1]", $decoup[2]); if (my $ERR=RRDs::error) { wlog ("$middle[1] ($decoup[2]) : $ERR",0); } } elsif ($middle[0] eq 'sql') # data pour base MySQL { my $dbh=DBI->connect("DBI:mysql:$middle[1]:localhost", "root", "") or wlog("$middle[2] :$DBI::errstr",0); $dbh->do("replace into $middle[2] values ($decoup[2])") or wlog("MySQL : $middle[2] : pb insert ($decoup[2]) : *($DBI::errstr)*",0); $dbh->disconnect(); } } # Cree un nouveau fils sub creer_un_nv_fils { my $pid; wlog("fork : $!", 1) unless defined ($pid=fork); if ($pid) # Code du pere { $processus_fils{$pid}=1; $processus_fils++; return; } else # code du fils { for (my $i=0;$i<$NB_MAX_CLIENTS_PAR_FILS;$i++) { $client=$serveur->accept() or last; traitement(<$client>); } } exit(1); # le fils a traite son nbre de connexion, il se tue } sub MOISSONNEUSE # gestionnaire des signaux SIGCHILD { $SIG{CHLD} = \&MOISSONNEUSE; do { my $pid = wait; # recuperation du PID du process fils mort... et maj des variables associees $processus_fils--; delete $processus_fils{$pid}; } } sub GEST_SIGNAUX # gestionnaire des signaux SIGINT, SIGHUP et SIGTERM { local($SIG{CHLD}) = 'IGNORE'; # désactivation des signaux SIGCHLD kill 'INT' => keys %processus_fils; # je suis un démon bien élevé : fin des fils avant arrêt wlog("Fin du programme", 1); } #******************************************************* ################# Programme principal ################## #******************************************************* wlog("Démarrage du programme serveur",0); # DEMONISATION DU SERVEUR chdir('/') || wlog("pb chdir",1); # ne pas bloquer le syst. de fichier de lancement (umount par ex.) open STDIN, '/dev/null' or wlog("Can't read /dev/null: $!",1); open STDOUT, '>/dev/null' or wlog("Can't write to /dev/null: $!",1); defined(my $pid_daemon = fork) || wlog("fork impossible : $!",1); exit if $pid_daemon; POSIX::setsid() or wlog("Erreur setsid : $!", 1); # detachement du groupe de processus open STDERR, '>&STDOUT' or wlog("Can't dup stdout: $!",1); # RECUPERATION DU NUMERO DE PORT TCP A UTILISER (my $port_serveur = getservbyname('TCPReport', 'tcp')) || wlog("echec getservbyname : $!", 1); # CREER LA SOCKET SERVEUR $serveur = IO::Socket::INET->new(LocalPort => $port_serveur, Type => SOCK_STREAM, Reuse => 1, Listen => 10) or wlog("le serveur TCP/IP n'a pu être démarré : $!", 1); # CREER LES PROCESSUS FILS for (1 .. $PREFORK) { creer_un_nv_fils(); } # INSTALLER LES GESTIONNAIRE DE SIGNAUX $SIG{CHLD} = \&MOISSONNEUSE; $SIG{INT} = $SIG{TERM} = $SIG{HUP} = \&GEST_SIGNAUX; # BOUCLE INFINIE : LE SERVEUR NE FAIT PLUS QUE SURVEILLER LA POPULATION DE FILS while (1) { sleep(); # attend un signal pour se reveiller (par ex. la mort d'un fils) # MAINTENIR LA POPULATION DE FILS for (my $i=$processus_fils;$i<$PREFORK;$i++) { creer_un_nv_fils(); } }