.\" Automatically generated by Pod::Man 2.06 (Pod::Simple 3.04) .\" .\" Standard preamble: .\" ======================================================================== .de Sh \" Subsection heading .br .if t .Sp .ne 5 .PP \fB\\$1\fR .PP .. .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. | will give a .\" real vertical bar. \*(C+ will give a nicer C++. Capital omega is used to .\" do unbreakable dashes and therefore won't be available. \*(C` and \*(C' .\" expand to `' in nroff, nothing in troff, for use with C<>. .tr \(*W-|\(bv\*(Tr .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' 'br\} .\" .\" If the F register is turned on, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . nr % 0 . rr F .\} .\" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .hy 0 .if n .na .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. . \" fudge factors for nroff and troff .if n \{\ . ds #H 0 . ds #V .8m . ds #F .3m . ds #[ \f1 . ds #] \fP .\} .if t \{\ . ds #H ((1u-(\\\\n(.fu%2u))*.13m) . ds #V .6m . ds #F 0 . ds #[ \& . ds #] \& .\} . \" simple accents for nroff and troff .if n \{\ . ds ' \& . ds ` \& . ds ^ \& . ds , \& . ds ~ ~ . ds / .\} .if t \{\ . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' .\} . \" troff and (daisy-wheel) nroff accents .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' .ds 8 \h'\*(#H'\(*b\h'-\*(#H' .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] .ds ae a\h'-(\w'a'u*4/10)'e .ds Ae A\h'-(\w'A'u*4/10)'E . \" corrections for vroff .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' . \" for low resolution devices (crt and lpr) .if \n(.H>23 .if \n(.V>19 \ \{\ . ds : e . ds 8 ss . ds o a . ds d- d\h'-1'\(ga . ds D- D\h'-1'\(hy . ds th \o'bp' . ds Th \o'LP' . ds ae ae . ds Ae AE .\} .rm #[ #] #H #V #F C .\" ======================================================================== .\" .IX Title "PERLIPC 1" .TH PERLIPC 1 "2006-03-03" "DocFr" "User Contributed Perl Documentation" .SH "NAME/NOM" .IX Header "NAME/NOM" perlipc \- Communication inter-processus en Perl (signaux, files d'attente, tubes, sous-processus su\*^rs, sockets et se\*'maphores) .SH "DESCRIPTION" .IX Header "DESCRIPTION" Les e\*'quipements \s-1CIP\s0 (\s-1IPC\s0 en anglais, \s-1NDT\s0) de base de Perl sont construits sur les signaux, tubes nomme\*'s, tubes ouverts, routines de socket de Berkeley et appels \s-1CIP\s0 du System V, du bon vieil Unix. Chacun est utilise\*' dans des situations le\*'ge\*`rement diffe\*'rentes. .SH "Signaux" .IX Header "Signaux" Perl utilise un mode\*`le simple de manipulation des signaux : le hachage \&\f(CW%SIG\fR contient les noms des handlers (gestionnaires) de signaux installe\*'s par l'utilisateur, ou des re\*'fe\*'rences vers eux. Ces handlers seront appele\*'s avec un argument qui est le nom du signal qui l'a de\*'clenche\*'. Un signal peut e\*^tre ge\*'ne\*'re\*' intentionnellement par une se\*'quence particulie\*`re au clavier comme control-C ou control\-Z, ou vous e\*^tre envoye\*' par un autre processus, ou e\*^tre de\*'clenche\*' automatiquement par le noyau lorsque des e\*'ve\*'nements spe\*'ciaux se produisent, comme la fin d'un processus fils, ou l'e\*'puisement de l'espace disponible sur la pile de votre processus, ou la rencontre d'une limite sur la taille d'un fichier. .PP Par exemple, pour capturer un signal d'interruption, installez un handler comme ceci. Faites aussi peu de choses que possible dans votre handler ; notez comment nous nous de\*'brouillons pour ne faire que placer une variable globale et lever une exception. C'est parce que sur la plupart des syste\*`mes, les bibliothe\*`ques ne sont pas re\*'entrantes, en particulier l'allocation de me\*'moire et les routines d'entre\*'e/sortie. Cela signifie que pratiquement \fIquoi que ce soit\fR dans votre handler pourrait en the\*'orie provoquer une faute de segmentation et par conse\*'quent un coredump. .PP .Vb 7 \& sub catch_zap { \& my $signame = shift; \& $shucks++; \& die "Somebody sent me a SIG$signame"; \& } \& $SIG{INT} = 'catch_zap'; # pourrait echouer dans un module \& $SIG{INT} = \e&catch_zap; # meilleur strategie .Ve .PP Les noms des signaux sont ceux qui sont liste\*'s par \f(CW\*(C`kill \-l\*(C'\fR sur votre syste\*`me, ou que vous pouvez obtenir via le module Config. Installez une liste \f(CW@signame\fR indexe\*'e par nume\*'ro pour avoir le nom et une table \f(CW%signo\fR indexe\*'e par le nom pour avoir le nume\*'ro : .PP .Vb 7 \& use Config; \& defined $Config{sig_name} || die "No sigs?"; \& foreach $name (split(' ', $Config{sig_name})) { \& $signo{$name} = $i; \& $signame[$i] = $name; \& $i++; \& } .Ve .PP Donc pour ve\*'rifier si le signal 17 et \s-1SIGALRM\s0 sont les me\*^mes, faites juste ceci : .PP .Vb 4 \& print "signal #17 = $signame[17]\en"; \& if ($signo{ALRM}) { \& print "SIGALRM is $signo{ALRM}\en"; \& } .Ve .PP Vous pouvez aussi choisir d'affecter les chai\*^nes \f(CW'IGNORE'\fR ou \&\f(CW'DEFAULT'\fR comme handler, dans ce cas Perl essaiera d'ignorer le signal ou de re\*'aliser l'action par de\*'faut. .PP Sur la plupart des plateformes Unix, le signal \f(CW\*(C`CHLD\*(C'\fR (parfois aussi connu sous le nom \f(CW\*(C`CLD\*(C'\fR) a un comportement spe\*'cial en fonction de la valeur \f(CW'IGNORE'\fR. Mettre \f(CW$SIG{CHLD}\fR a\*` \f(CW'IGNORE'\fR sur une telle plateforme a pour effet de ne pas cre\*'er de processus zombie lorsque le processus pe\*`re e\*'choue dans l'attente (\f(CW\*(C`wait()\*(C'\fR) de son fils (i.e. les processus fils sont automatiquement de\*'truits). Appeler \f(CW\*(C`wait()\*(C'\fR avec \&\f(CW$SIG{CHLD}\fR place\*' a\*` \f(CW'IGNORE'\fR retourne habituellement \f(CW\*(C`\-1\*(C'\fR sur de telles plateformes. .PP Certains signaux ne peuvent e\*^tre ni pie\*'ge\*'s ni ignore\*'s, comme les signaux \s-1KILL\s0 et \s-1STOP\s0 (mais pas \s-1TSTP\s0). Une strate\*'gie pour ignorer temporairement des signaux est d'utiliser une de\*'claration \fIlocal()\fR, qui sera automatiquement restaure\*'e a\*` la sortie de votre bloc d'instructions (Souvenez\-vous que les valeurs \fIlocal()\fR sont \*(L"he\*'rite\*'es\*(R" par les fonctions appele\*'es depuis ce bloc). .PP .Vb 7 \& sub precious { \& local $SIG{INT} = 'IGNORE'; \& &more_functions; \& } \& sub more_functions { \& # interrupts still ignored, for now... \& } .Ve .PP Envoyer un signal a\*` un identifiant de processus ne\*'gatif signifie que vous envoyez ce signal a\*` tout le groupe de processus Unix. Ce code envoie un signal hang-up a\*` tous les processus du groupe courant (et place \f(CW$SIG\fR{\s-1HUP\s0} sur \s-1IGNORE\s0 pour qu'il ne se tue pas lui\-me\*^me) : .PP .Vb 5 \& { \& local $SIG{HUP} = 'IGNORE'; \& kill HUP => \-$$; \& # snazzy writing of: kill('HUP', \-$$) \& } .Ve .PP Un autre signal inte\*'ressant a\*` envoyer est le signal nume\*'ro ze\*'ro. Il n'affecte en ve\*'rite\*' aucun autre processus, mais ve\*'rifie s'il est encore en vie ou a change\*' son \s-1UID\s0. .PP .Vb 3 \& unless (kill 0 => $kid_pid) { \& warn "something wicked happened to $kid_pid"; \& } .Ve .PP Vous pourriez aussi vouloir employer des fonctions anonymes comme handlers de signaux simples : .PP .Vb 1 \& $SIG{INT} = sub { die "\enOutta here!\en" }; .Ve .PP Mais cela sera proble\*'matique pour les handlers plus complique\*'s qui ont besoin de se re\*'installer eux\-me\*^mes. Puisque le me\*'canisme de signalisation de Perl est base\*' sur la fonction \fIsignal\fR\|(3) de la bibliothe\*`que C, vous pourriez parfois avoir la malchance d'utiliser des syste\*`mes ou\*` cette fonction est \*(L"casse\*'e\*(R", c'est\-a\*`\-dire qu'elle se comporte selon l'ancienne fac\*,on peu fiable du SysV pluto\*^t que la plus re\*'cente et plus raisonnable me\*'thode \s-1BSD\s0 et \s-1POSIX\s0. Vous verrez donc les gens sur la de\*'fensive e\*'crire des handlers de signaux ainsi : .PP .Vb 9 \& sub REAPER { \& $waitedpid = wait; \& # maudissez sysV : il nous force non seulement a \& # reinstaller le handler, mais aussi a le placer \& # apres le wait \& $SIG{CHLD} = \e&REAPER; \& } \& $SIG{CHLD} = \e&REAPER; \& # maintenant, on fait quelque chose qui forke... .Ve .PP Ou me\*^me de cette fac\*,on plus e\*'labore\*'e : .PP .Vb 10 \& use POSIX ":sys_wait_h"; \& sub REAPER { \& my $child; \& while (($child = waitpid(\-1,WNOHANG)) { \& $Kid_Status{$child} = $?; \& } \& $SIG{CHLD} = \e&REAPER; # maudissez encore sysV \& } \& $SIG{CHLD} = \e&REAPER; \& # on fait quelque chose qui forke... .Ve .PP La manipulation de signaux est aussi utilise\*'e pour les timeouts sous Unix. Pendant que vous e\*^tes soigneusement prote\*'ge\*' par un bloc \&\f(CW\*(C`eval{}\*(C'\fR, vous installez un handler de signal pour pie\*'ger les signaux d'alarme puis programmez de vous en faire de\*'livrer un dans un certain nombre de secondes. Puis vous essayez votre ope\*'ration bloquante, annulant l'alarme lorsque c'est fait mais pas avant d'e\*^tre sorti de votre bloc \f(CW\*(C`eval{}\*(C'\fR. Si cela ne marche pas, vous utiliserez \fIdie()\fR pour sauter hors du bloc, tout comme vous utiliseriez \fIlongjmp()\fR ou \&\fIthrow()\fR dans d'autres langages. .PP Voici un exemple : .PP .Vb 7 \& eval { \& local $SIG{ALRM} = sub { die "alarm clock restart" }; \& alarm 10; \& flock(FH, 2); # lock d'ecriture blocante \& alarm 0; \& }; \& if ($@ and $@ !~ /alarm clock restart/) { die } .Ve .PP Si l'ope\*'ration chronome\*'tre\*'e est \fIsystem()\fR ou \fIqX()\fR, cette technique peut ge\*'ne\*'rer des zombies. Si cela vous pre\*'occupe, vous devrez faire vos propres \fIfork()\fR et \fIexec()\fR, et tuer les processus fils errants. .PP Pour une manipulation plus complexe des signaux, vous pourriez voir le module du standard \s-1POSIX\s0. Il est lamentable que Ceci soit presque entie\*`rement non documente\*', mais le fichier \fIt/lib/posix.t\fR de la distribution source de Perl contient quelques exemples. .SH "Tubes nomme\*'s" .IX Header "Tubes nomme's" Un tube nomme\*' (souvent appele\*' une file d'attente \s-1FIFO\s0) est un vieux me\*'canisme de \s-1CIP\s0 Unix pour les processus communicant sur la me\*^me machine. Il fonctionne tout comme un tube anonyme, normal et connecte\*', sauf que les processus se donnent rendez-vous en utilisant un nom de fichier et n'ont pas besoin d'e\*^tre apparente\*'s. .PP Pour cre\*'er un tube nomme\*', utilisez la commande Unix \fImknod\fR\|(1) ou sur certains syste\*`mes, \fImkfifo\fR\|(1). Elles peuvent ne pas se trouver dans votre chemin d'acce\*`s normal. .PP .Vb 8 \& # le systeme renvoit les vals a l'envers, donc && et pas || \& # \& $ENV{PATH} .= ":/etc:/usr/etc"; \& if ( system('mknod', $path, 'p') \& && system('mkfifo', $path) ) \& { \& die "mk{nod,fifo} $path failed"; \& } .Ve .PP Une \s-1FIFO\s0 est pratique quand vous voulez connecter un processus a\*` un autre sans qu'ils soient apparente\*'s. Lorsque vous ouvrez une \s-1FIFO\s0, le programme se bloque jusqu'a\*` ce que quelque chose arrive a\*` l'autre bout. .PP Par exemple, disons que vous aimeriez que votre fichier \fI.signature\fR soit un tube nomme\*' connecte\*' a\*` un programme en Perl. De\*'sormais, chaque fois qu'un programme (comme un gestionnaire de courrier e\*'lectronique, un lecteur de news, un finger, etc.) essaye de lire le contenu de ce fichier, il se bloque et votre programme lui fournit une nouvelle signature. Nous utiliserons le test de fichier \fB\-p\fR qui teste un tube pour de\*'terminer si quelqu'un (ou quelque chose) a accidentellement supprime\*' notre \s-1FIFO\s0. .PP .Vb 3 \& chdir; # a la maison \& $FIFO = '.signature'; \& $ENV{PATH} .= ":/etc:/usr/games"; \& \& while (1) { \& unless (\-p $FIFO) { \& unlink $FIFO; \& system('mknod', $FIFO, 'p') \& && die "can't mknod $FIFO: $!"; \& } \& \& # la ligne suivante bloque jusqu'a ce qu'il y ait un lecteur \& open (FIFO, "> $FIFO") || die "can't write $FIFO: $!"; \& print FIFO "John Smith (smith\e@host.org)\en", `fortune \-s`; \& close FIFO; \& sleep 2; # pour eviter les signaux dupliques \& } .Ve .Sh "\s-1AVERTISSEMENT\s0" .IX Subsection "AVERTISSEMENT" En installant du code Perl qui traite des signaux, vous vous exposer a\*` deux types de dangers. D'abord, peu de fonctions de la bibliothe\*`que syste\*`me sont re\*'entrantes. Si le signal provoque une interruption pendant que Perl exe\*'cute une fonction (comme \fImalloc\fR\|(3) ou \fIprintf\fR\|(3)), et que votre handler de signal appelle alors la me\*^me fonction, vous pourriez obtenir un comportement non pre\*'visible \- souvent, un core dump. Ensuite, Perl n'est pas lui\-me\*^me re\*'entrant aux niveaux les plus bas. Si le signal interrompt Perl pendant qu'il change ses propres structures de donne\*'es internes, un comportement non pre\*'visible similaire peut apparai\*^tre. .PP Vous pouvez faire deux choses, suivant que vous e\*^tes paranoi\*:aque ou pragmatique. L'approche paranoi\*:aque est d'en faire aussi peu que possible dans votre handler. Fixez une variable entie\*`re existant et ayant de\*'ja\*` une valeur et revenez. Ceci ne vous aide pas si vous e\*^tes au milieu d'un appel syste\*`me lent, qui se contentera de recommencer. Cela veut dire que vous devez mourir avec \f(CW\*(C`die\*(C'\fR pour sauter (\fIlongjump\fR\|(3)) hors du handler. Me\*^me ceci est quelque peu cavalier pour le ve\*'ritable paranoi\*:aque, qui e\*'vite \f(CW\*(C`die\*(C'\fR dans un handler car le syste\*`me \fIest\fR la\*` pour vous attraper. L'approche pragmatique consiste a\*` dire \*(L"je connais les risques, mais je pre\*'fe\*`re la facilite\*'\*(R", et ensuite faire tout ce qu'on veut dans le handler de signal, en e\*'tant pre\*^t a\*` nettoyer les coredumps de temps en temps. .PP Interdire totalement les handlers interdirait aussi beaucoup de programmes inte\*'ressants, y compris virtuellement tout ce qui se trouve dans cette page de manuel, puisque vous ne pourriez plus e\*'crire ne serait-ce qu'un handler \s-1SIGCHLD\s0. Ce proble\*`me e\*'pineux devrait e\*^tre re\*'gle\*' dans la version 5.005. .SH "Utilisation de \fIopen()\fP pour la CIP" .IX Header "Utilisation de open() pour la CIP" l'instruction \fIopen()\fR de base en Perl peut aussi e\*^tre utilise\*'e pour la communication inter-processus unidirectionnelle en ajoutant un symbole de tube juste avant ou apre\*`s le second argument de \fIopen()\fR. Voici comment de\*'marrer un processus fils dans lequel vous avez l'intention d'e\*'crire : .PP .Vb 5 \& open(SPOOLER, "| cat \-v | lpr \-h 2>/dev/null") \& || die "can't fork: $!"; \& local $SIG{PIPE} = sub { die "spooler pipe broke" }; \& print SPOOLER "stuff\en"; \& close SPOOLER || die "bad spool: $! $?"; .Ve .PP Et voici comment de\*'marrer un processus fils depuis lequel vous de\*'sirez lire : .PP .Vb 7 \& open(STATUS, "netstat \-an 2>&1 |") \& || die "can't fork: $!"; \& while () { \& next if /^(tcp|udp)/; \& print; \& } \& close STATUS || die "bad netstat: $! $?"; .Ve .PP Si l'on peut etre certain qu'un programme specifique est un script Perl que attend des noms de fichiers dans \f(CW@ARGV\fR, le programmeur fute\*' peut e\*'crire quelque chose comme ceci : .PP .Vb 1 \& % program f1 "cmd1|" \- f2 "cmd2|" f3 < tmpfile .Ve .PP et, inde\*'pendemment du shell d'ou\*` il est appele\*', le programme Perl lira dans le fichier \fIf1\fR, le processus \fIcmd1\fR, l'entre\*'e standard (\fItmpfile\fR dans ce cas), le fichier \fIf2\fR, la commande \fIcmd2\fR, et finalement dans le fichier \fIf3\fR. Pluto\*^t de\*'brouillard, hein ? .PP Vous pourriez remarquer que l'on pourrait utiliser des accents graves pour obtenir a\*` peu pre\*`s le me\*^me effet que l'ouverture d'un tube en lecture : .PP .Vb 2 \& print grep { !/^(tcp|udp)/ } `netstat \-an 2>&1`; \& die "bad netstat" if $?; .Ve .PP Me\*^me si ceci est vrai en surface, il est bien plus efficace de traiter le fichier une ligne ou un enregistrement a\*` la fois car alors vous n'avez pas a\*` charger toute la chose en me\*'moire d'un seul coup. Cela vous donne aussi un contro\*^le plus fin sur tout le processus, vous laissant tuer le processus fils plus to\*^t si vous le voulez. .PP Prenez soin de ve\*'rifier a\*` la fois les valeurs de retour de \fIopen()\fR et de \fIclose()\fR. Si vous \fIe\*'crivez\fR dans un tube, vous pouvez aussi pie\*'ger \&\s-1SIGPIPE\s0. Sinon, pensez a\*` ce qui se passe lorsque vous ouvrez un tube vers une commande qui n'existe pas : le \fIopen()\fR re\*'ussira tre\*`s probablement (il ne fait que refle\*'ter le succe\*`s du \fIfork()\fR), mais ensuite votre sortie e\*'chouera \- spectaculairement. Perl ne peut pas savoir si la commande a fonctionne\*' parce que votre commande tourne en fait dans un processus se\*'pare\*' dont l'\fIexec()\fR peut avoir e\*'choue\*'. Par conse\*'quent, ceux qui lisent depuis une commande bidon retournent juste une rapide fin de fichier, ceux qui e\*'crivent vers une commande bidon provoqueront un signal qu'ils feraient mieux d'e\*^tre pre\*'pare\*'s a\*` ge\*'rer. Conside\*'rons : .PP .Vb 3 \& open(FH, "|bogus") or die "can't fork: $!"; \& print FH "bang\en" or die "can't write: $!"; \& close FH or die "can't close: $!"; .Ve .PP Ceci n'explosera pas avant le close, et ce sera sur un \s-1SIGPIPE\s0. Pour l'intercepter, vous pourriez utiliser ceci : .PP .Vb 4 \& $SIG{PIPE} = 'IGNORE'; \& open(FH, "|bogus") or die "can't fork: $!"; \& print FH "bang\en" or die "can't write: $!"; \& close FH or die "can't close: status=$?"; .Ve .Sh "Handles de Fichiers" .IX Subsection "Handles de Fichiers" Le processus principal et tous les processus fils qu'il peut ge\*'ne\*'rer partagent les me\*^mes handles de fichiers \s-1STDIN\s0, \s-1STDOUT\s0 et \s-1STDERR\s0. Si deux processus essayent d'y acce\*'der en me\*^me temps, des choses e\*'tranges peuvent se produire. Vous pourriez aussi fermer ou re\*'ouvrir les handles de fichiers pour le fils. Vous pouvez contourner cela en ouvrant votre tube avec \fIopen()\fR, mais sur certains syste\*`mes cela signifie que le processus fils ne peut pas survivre a\*` son pe\*`re. .Sh "Processus en Arrie\*`re\-Plan." .IX Subsection "Processus en Arrie`re-Plan." Vous pouvez exe\*'cuter une commande en arrie\*`re\-plan avec : .PP .Vb 1 \& system("cmd &"); .Ve .PP Les \s-1STDOUT\s0 et \s-1STDERR\s0 de la commande (et peut\-e\*^tre \s-1STDIN\s0, selon votre shell) seront les me\*^mes que ceux du pe\*`re. Vous n'aurez pas besoin de pie\*'ger \s-1SIGCHLD\s0 a\*` cause du double fork qui se produit (voir plus bas pour plus de de\*'tails). .Sh "Dissociation Comple\*`te du Fils et de son Pe\*`re" .IX Subsection "Dissociation Comple`te du Fils et de son Pe`re" Dans certains cas (de\*'marrage de processus serveurs, par exemple) vous voudrez comple\*`tement dissocier le processus fils de son pe\*`re. Ceci est souvent appele\*' une de\*'monisation. Un de\*'mon bien e\*'leve\*' fera aussi un \&\fIchdir()\fR vers le re\*'pertoire root (pour qu'il n'empe\*^che pas le de\*'montage du syste\*`me de fichiers contenant le re\*'pertoire a\*` partir duquel il a e\*'te\*' lance\*') et redirige ses descripteurs de fichier standard vers \&\fI/dev/null\fR (pour que des sorties ale\*'atoires ne finissent pas sur le terminal de l'utilisateur). .PP .Vb 1 \& use POSIX 'setsid'; \& \& sub daemonize { \& chdir '/' or die "Can't chdir to /: $!"; \& open STDIN, '/dev/null' or die "Can't read /dev/null: $!"; \& open STDOUT, '>/dev/null' \& or die "Can't write to /dev/null: $!"; \& defined(my $pid = fork) or die "Can't fork: $!"; \& exit if $pid; \& setsid or die "Can't start a new session: $!"; \& open STDERR, '>&STDOUT' or die "Can't dup stdout: $!"; \& } .Ve .PP Le \fIfork()\fR doit venir avant le \fIsetsid()\fR pour s'assurer que vous n'e\*^tes pas un leader de groupe de processus (le \fIsetsid()\fR e\*'chouera si vous l'e\*^tes). Si votre syste\*`me ne posse\*`de pas la fonction \fIsetsid()\fR, ouvrez \&\fI/dev/tty\fR et utilisez dessus l'\fIioctl()\fR \f(CW\*(C`TIOCNOTTY\*(C'\fR a\*` la place. Voir \fItty\fR\|(4) pour plus de de\*'tails. .PP Les utilisateurs non unixiens devraient jeter un oeil sur leur module Votre_OS::Process pour avoir d'autres solutions. .Sh "Ouvertures Su\*^res d'un Tube" .IX Subsection "Ouvertures Su^res d'un Tube" Une autre approche inte\*'ressante de la \s-1CIP\s0 est de rendre votre simple programme multiprocessus et de communiquer entre (ou me\*^me parmi) eux. La fonction \fIopen()\fR acceptera un fichier en argument pour \f(CW"\-|"\fR ou \f(CW"|\-"\fR afin de faire une chose tre\*`s inte\*'ressante : elle forkera un fils connecte\*' au descripteur de fichier que vous venez d'ouvrir. Le fils exe\*'cute le me\*^me programme que le parent. C'est utile par exemple pour ouvrir de fac\*,on se\*'curise\*'e un fichier lorsque l'on s'exe\*'cute sous un \s-1UID\s0 ou un \s-1GID\s0 pre\*'sume\*'. Si vous ouvrez un tube \fIvers\fR minus, vous pouvez e\*'crire dans le descripteur de fichier que vous avez ouvert et votre fils le trouvera dans son \s-1STDIN\s0. Si vous ouvrez un tube \&\fIdepuis\fR minus, vous pouvez lire depuis le descripteur de fichier tout ce que votre fils e\*'crit dans son \s-1STDOUT\s0. .PP .Vb 2 \& use English; \& my $sleep_count = 0; \& \& do { \& $pid = open(KID_TO_WRITE, "|\-"); \& unless (defined $pid) { \& warn "cannot fork: $!"; \& die "bailing out" if $sleep_count++ > 6; \& sleep 10; \& } \& } until defined $pid; \& \& if ($pid) { # parent \& print KID_TO_WRITE @some_data; \& close(KID_TO_WRITE) || warn "kid exited $?"; \& } else { # child \& ($EUID, $EGID) = ($UID, $GID); # suid progs only \& open (FILE, "> /safe/file") \& || die "can't open /safe/file: $!"; \& while () { \& print FILE; # child's STDIN is parent's KID \& } \& exit; # n'oubliez pas ceci \& } .Ve .PP Un autre usage courant de cette construction apparai\*^t lorsque vous avez besoin d'exe\*'cuter quelque chose sans interfe\*'rences de la part du shell. Avec \fIsystem()\fR, c'est direct, mais vous ne pouvez pas utiliser un tube ouvert ou des accents graves de fac\*,on su\*^re. C'est parce qu'il n'y a pas de moyen d'empe\*^cher le shell de mettre son nez dans vos arguments. A\*` la place, utilisez le contro\*^le de bas niveau pour appeler \&\fIexec()\fR directement. .PP Voici un backtick ou un tube ouvert en lecture su\*^rs : .PP .Vb 2 \& # ajoutez un traitement d'erreur comme ci\-dessus \& $pid = open(KID_TO_READ, "\-|"); \& \& if ($pid) { # parent \& while () { \& # faites quelque chose d'inte\*'ressant \& } \& close(KID_TO_READ) || warn "kid exited $?"; \& \& } else { # fils \& ($EUID, $EGID) = ($UID, $GID); # suid uniquement \& exec($program, @options, @args) \& || die "can't exec program: $!"; \& # PASATTEINT \& } .Ve .PP Et voici un tube su\*^r ouvert en e\*'criture : .PP .Vb 3 \& # ajoutez un traitement d'erreur comme ci\-dessus \& $pid = open(KID_TO_WRITE, "|\-"); \& $SIG{ALRM} = sub { die "whoops, $program pipe broke" }; \& \& if ($pid) { # parent \& for (@data) { \& print KID_TO_WRITE; \& } \& close(KID_TO_WRITE) || warn "kid exited $?"; \& \& } else { # fils \& ($EUID, $EGID) = ($UID, $GID); \& exec($program, @options, @args) \& || die "can't exec program: $!"; \& # PASATTEINT \& } .Ve .PP Notez que ces ope\*'rations sont des forks Unix complets, ce qui signifie qu'ils peuvent ne pas e\*^tre correctement imple\*'mente\*'s sur des syste\*`mes e\*'trangers. De plus, il ne s'agit pas d'un vrai multithreading. Si vous de\*'sirez en apprendre plus sur le threading, voyez le fichier \&\fImodules\fR mentionne\*'e plus bas dans la section \s-1VOIR\s0 \s-1AUSSI\s0. .Sh "Communication Bidirectionnelle avec un autre Processus" .IX Subsection "Communication Bidirectionnelle avec un autre Processus" Me\*^me si ceci fonctionne raisonnablement bien pour la communication unidirectionnelle, qu'en est-il pour la communication bidirectionnelle ? La chose la plus e\*'vidente que vous aimeriez faire ne marche en fait pas : .PP .Vb 1 \& open(PROG_FOR_READING_AND_WRITING, "| un programme |") .Ve .PP et si vous oubliez d'utiliser le pragma \f(CW\*(C`use warnings\*(C'\fR ou l'option \&\fB\-w\fR, alors vous raterez comple\*`tement le message de diagnostic : .PP .Vb 1 \& Can't do bidirectional pipe at \-e line 1. .Ve .PP Si vous le voulez vraiment, vous pouvez utiliser la fonction de la bibliothe\*`que standard \fIopen2()\fR pour attraper les deux bouts. Il existe aussi une \fIopen3()\fR pour les E/S tridirectionnelles de fac\*,on que vous puissiez aussi re\*'cupe\*'rer le \s-1STDERR\s0 de votre fils, mais faire ceci ne\*'cessiterait une boucle \fIselect()\fR maladroite et ne vous permettrait pas d'utiliser les ope\*'rations d'entre\*'e normales de Perl. .PP Si vous jetez un oeil sur son source, vous verrez que \fIopen2()\fR utilise des primitives de bas niveau, comme les appels \fIpipe()\fR et \fIexec()\fR d'Unix, pour cre\*'er toutes les connexions. Il aurait peut\-e\*^tre e\*'te\*' un peu plus efficace d'utiliser \fIsocketpair()\fR, mais cela serait devenu encore moins portable que ce ne l'est de\*'ja\*`. Les fonctions \fIopen2()\fR et \&\fIopen3()\fR ont peu de chances de fonctionner ailleurs que sur un syste\*`me Unix ou quelques autres pre\*'tendant e\*^tre conformes a\*` la norme \s-1POSIX\s0. .PP Voici un exemple d'utilisation de \fIopen2()\fR: .PP .Vb 5 \& use FileHandle; \& use IPC::Open2; \& $pid = open2(*Reader, *Writer, "cat \-u \-n" ); \& print Writer "stuff\en"; \& $got = ; .Ve .PP Le proble\*`me avec ceci est que le buffering d'Unix va vraiment rendre cette ope\*'ration pe\*'nible. Me\*^me si votre descripteur de fichier \f(CW\*(C`Writer\*(C'\fR est vide\*' automatiquement, et me\*^me si le processus a\*` l'autre bout rec\*,oit vos donne\*'es de fac\*,on re\*'gulie\*`re, vous ne pouvez habituellement rien faire pour le forcer a\*` vous les renvoyer rapidement de la me\*^me fac\*,on. Dans ce cas\-ci, nous le pouvons, car nous avons donne\*' a\*` \fIcat\fR une option \fB\-u\fR pour qu'il n'ait pas de tampon. Mais tre\*`s peu de commandes Unix sont conc\*,ues pour fonctionner a\*` travers des tubes, ceci marche donc rarement a\*` moins que vous ayez e\*'crit vous\-me\*^mes le programme se trouvant a\*` l'autre bout du tube a\*` deux sens. .PP Une solution a\*` ceci est la bibliothe\*`que non standard \fIComm.pl\fR. Elle utilise des pseudo-ttys pour rendre le comportement de votre programme plus raisonnable : .PP .Vb 6 \& require 'Comm.pl'; \& $ph = open_proc('cat \-n'); \& for (1..10) { \& print $ph "a line\en"; \& print "got back ", scalar <$ph>; \& } .Ve .PP De cette fac\*,on vous n'avez pas besoin de contro\*^ler le code source du programme que vous utilisez. La bibliothe\*`que \fIComm\fR contient aussi les fonctions \fIexpect()\fR et \fIinteract()\fR. Vous trouverez la bibliothe\*`que (et, nous l'espe\*'rons, son successeur \fIIPC::Chat\fR) dans l'archive \s-1CPAN\s0 la plus proche de vous, comme il est de\*'taille\*' dans la section \s-1VOIR\s0 \&\s-1AUSSI\s0 ci\-dessous. .PP Le module plus re\*'cent Expect.pm sur le \s-1CPAN\s0 s'occupe aussi de ce genre de choses. Ce module ne\*'cessite deux autres modules du \s-1CPAN\s0 : IO::Pty et IO::Stty. Il installe un pseudo-terminal pour interagir avec les programmes qui insistent pour discuter avec le pilote de pe\*'riphe\*'rique du terminal. Si votre syste\*`me fait partie de ceux supporte\*'s, cela pourrait e\*^tre la meilleure solution pour vous. .Sh "Communication Bidirectionnelle avec Vous\-me\*^me" .IX Subsection "Communication Bidirectionnelle avec Vous-me^me" Si vous voulez, vous pouvez faire des \fIpipe()\fR et des \fIfork()\fR de bas niveau pour coudre tout cela ensemble a\*` la main. Cet exemple ne se parle qu'a\*` lui\-me\*^me, mais vous pourriez re\*'ouvrir les handles approprie\*'s vers \s-1STDIN\s0 et \s-1STDOUT\s0 et appeler d'autres processus. .PP .Vb 8 \& #!/usr/bin/perl \-w \& # pipe1 \- communication bidirectionnelle utilisant deux paires \& # de tubes concus pour les syste\*`mes n'ayant pas l'appel socketpair() \& use IO::Handle; # milliers de lignes juste pour l'autoflush :\-( \& pipe(PARENT_RDR, CHILD_WTR); # XXX: echec ? \& pipe(CHILD_RDR, PARENT_WTR); # XXX: echec ? \& CHILD_WTR\->autoflush(1); \& PARENT_WTR\->autoflush(1); \& \& if ($pid = fork) { \& close PARENT_RDR; close PARENT_WTR; \& print CHILD_WTR "Parent Pid $$ is sending this\en"; \& chomp($line = ); \& print "Parent Pid $$ just read this: `$line'\en"; \& close CHILD_RDR; close CHILD_WTR; \& waitpid($pid,0); \& } else { \& die "cannot fork: $!" unless defined $pid; \& close CHILD_RDR; close CHILD_WTR; \& chomp($line = ); \& print "Child Pid $$ just read this: `$line'\en"; \& print PARENT_WTR "Child Pid $$ is sending this\en"; \& close PARENT_RDR; close PARENT_WTR; \& exit; \& } .Ve .PP Mais vous n'avez en fait pas besoin de faire deux appels a\*` des tubes. Si vous disposez de l'appel syste\*`me \fIsocketpair()\fR, il fera tout cela pour vous. .PP .Vb 3 \& #!/usr/bin/perl \-w \& # pipe2 \- communication bidirectionnelle utilisant socketpair \& # "les choses partage\*'es dans les deux sens sont toujours les meilleures" \& \& use Socket; \& use IO::Handle; # milliers de lignes juste pour l'autoflush :\-( \& # Nous utilisons AF_UNIX car bien que *_LOCAL est la forme \& # POSIX 1003.1g de la constante, beaucoup de machines ne \& # l'ont pas encore. \& socketpair(CHILD, PARENT, AF_UNIX, SOCK_STREAM, PF_UNSPEC) \& or die "socketpair: $!"; \& \& CHILD\->autoflush(1); \& PARENT\->autoflush(1); \& \& if ($pid = fork) { \& close PARENT; \& print CHILD "Parent Pid $$ is sending this\en"; \& chomp($line = ); \& print "Parent Pid $$ just read this: `$line'\en"; \& close CHILD; \& waitpid($pid,0); \& } else { \& die "cannot fork: $!" unless defined $pid; \& close CHILD; \& chomp($line = ); \& print "Child Pid $$ just read this: `$line'\en"; \& print PARENT "Child Pid $$ is sending this\en"; \& close PARENT; \& exit; \& } .Ve .SH "Sockets : Communication Client/Serveur" .IX Header "Sockets : Communication Client/Serveur" Me\*^me si elles ne sont pas limite\*'es aux syste\*`mes d'exploitation de\*'rive\*'s d'Unix (e.g., WinSock sur les \s-1PC\s0 fournit le support des sockets, tout comme le font les bibliothe\*`ques \s-1VMS\s0), vous ne disposez peut\-e\*^tre pas des sockets sur votre syste\*`me, auquel cas cette section ne vous fera probablement pas beaucoup de bien. Avec les sockets, vous pouvez a\*` la fois cre\*'er des circuits virtuels (i.e., des streams \s-1TCP\s0) et des datagrammes (i.e., des paquets \s-1UDP\s0). Vous pouvez me\*^me peut\-e\*^tre faire plus, en fonction de votre syste\*`me. .PP Les appels Perl pour traiter les sockets ont les me\*^mes noms que les appels syste\*`me correspondants en C, mais leurs arguments tendent a\*` e\*^tre diffe\*'rents pour deux raisons : tout d'abord, les handles de fichiers de Perl fonctionnent diffe\*'remment des descripteurs de fichiers en C. Ensuite, Perl connai\*^t de\*'ja\*` la longueur de ses chai\*^nes, vous n'avez donc pas besoin de passer cette information. .PP L'un des proble\*`mes majeurs avec l'ancien code des sockets de Perl e\*'tait qu'il utilisait des valeurs code\*'es en dur pour certaines des constantes, ce qui endommageait se\*'ve\*`rement la portabilite\*'. Si vous avez de\*'ja\*` vu du code qui fait des choses comme fixer explicitement \&\f(CW\*(C`$AF_INET = 2\*(C'\fR, vous savez que vous avez un gros proble\*`me : une approche incommensurablement supe\*'rieure est d'utiliser le module \&\f(CW\*(C`Socket\*(C'\fR, qui fournit un acce\*`s plus fiable aux diffe\*'rentes constantes et fonctions dont vous aurez besoin. .PP Si vous n'e\*^tes pas en train d'e\*'crire un couple serveur/client pour un protocole existant comme \s-1NNTP\s0 or \s-1SMTP\s0, vous devriez re\*'fle\*'chir a\*` la fac\*,on dont votre serveur saura quand le client a fini de parler, et vice\-versa. La plupart des protocoles sont base\*'s sur des messages et des re\*'ponses d'une ligne (de fac\*,on que l'une des parties sache que l'autre a fini quand un \*(L"\en\*(R" est rec\*,u) ou sur des messages et des re\*'ponses de plusieurs lignes qui se finissent par un point ou une ligne vide (\*(L"\en.\en\*(R" termine un message ou une re\*'ponse). .Sh "Terminateur de Ligne Internet" .IX Subsection "Terminateur de Ligne Internet" Le terminateur de ligne de l'Internet est \*(L"\e015\e012\*(R". Sous certaines variantes \s-1ASCII\s0 d'Unix, cela peut habituellement e\*^tre e\*'crit \*(L"\er\en\*(R", mais sous certains autres syste\*`mes, \*(L"\er\en\*(R" peut certaines fois e\*^tre \&\*(L"\e015\e015\e012\*(R", \*(L"\e012\e012\e015\*(R", ou quelque chose de comple\*`tement diffe\*'rent. Les standards spe\*'cifient d'e\*'crire \*(L"\e015\e012\*(R" pour e\*^tre conforme (soyez strict sur ce que vous fournissez), mais ils recommandent aussi d'accepter un \*(L"\e012\*(R" solitaire en entre\*'e (soyez indulgent sur ce que vous attendez). Nous n'avons pas toujours e\*'te\*' tre\*`s bon a\*` ce sujet dans le code de cette page de manuel, mais a\*` moins que vous ne soyez sur un Mac, cela devrait aller. .Sh "Clients et Serveurs \s-1TCP\s0 Internet" .IX Subsection "Clients et Serveurs TCP Internet" Utilisez des sockets Internet lorsque vous voulez effectuer une communication client-serveur qui pourrait s'e\*'tendre a\*` des machines hors de votre propre syste\*`me. .PP Voici un exemple de client \s-1TCP\s0 utilisant des sockets Internet : .PP .Vb 4 \& #!/usr/bin/perl \-w \& use strict; \& use Socket; \& my ($remote,$port, $iaddr, $paddr, $proto, $line); \& \& $remote = shift || 'localhost'; \& $port = shift || 2345; # port pris au hasard \& if ($port =~ /\eD/) { $port = getservbyname($port, 'tcp') } \& die "No port" unless $port; \& $iaddr = inet_aton($remote) || die "no host: $remote"; \& $paddr = sockaddr_in($port, $iaddr); \& \& $proto = getprotobyname('tcp'); \& socket(SOCK, PF_INET, SOCK_STREAM, $proto) || die "socket: $!"; \& connect(SOCK, $paddr) || die "connect: $!"; \& while (defined($line = )) { \& print $line; \& } \& \& close (SOCK) || die "close: $!"; \& exit; .Ve .PP Et voici le serveur correspondant pour l'accompagner. Nous laisserons l'adresse sous la forme \s-1INADDR_ANY\s0 pour que le noyau puisse choisir l'interface approprie\*'e sur les ho\*^tes a\*` plusieurs pattes. Si vous voulez rester sur une interface particulie\*`re (comme le co\*^te\*' externe d'une passerelle ou d'un firewall), vous devriez la remplacer par votre ve\*'ritable adresse. .PP .Vb 6 \& #!/usr/bin/perl \-Tw \& use strict; \& BEGIN { $ENV{PATH} = '/usr/ucb:/bin' } \& use Socket; \& use Carp; \& $EOL = "\e015\e012"; \& \& sub logmsg { print "$0 $$: @_ at ", scalar localtime, "\en" } \& \& my $port = shift || 2345; \& my $proto = getprotobyname('tcp'); \& $port = $1 if $port =~ /(\ed+)/; # nettoie le numero de port \& \& socket(Server, PF_INET, SOCK_STREAM, $proto) || die "socket: $!"; \& setsockopt(Server, SOL_SOCKET, SO_REUSEADDR, \& pack("l", 1)) || die "setsockopt: $!"; \& bind(Server, sockaddr_in($port, INADDR_ANY)) || die "bind: $!"; \& listen(Server,SOMAXCONN) || die "listen: $!"; \& \& logmsg "server started on port $port"; \& \& my $paddr; \& \& $SIG{CHLD} = \e&REAPER; \& \& for ( ; $paddr = accept(Client,Server); close Client) { \& my($port,$iaddr) = sockaddr_in($paddr); \& my $name = gethostbyaddr($iaddr,AF_INET); \& \& logmsg "connection from $name [", \& inet_ntoa($iaddr), "] \& at port $port"; \& \& print Client "Hello there, $name, it's now ", \& scalar localtime, $EOL; \& } .Ve .PP Et voici une version multithread. Elle est multithread en ce sens que comme la plupart des serveurs, elle ge\*'ne\*`re (fork) un serveur esclave pour manipuler la reque\*^te du client de fac\*,on que le serveur mai\*^tre puisse rapidement revenir servir un nouveau client. .PP .Vb 6 \& #!/usr/bin/perl \-Tw \& use strict; \& BEGIN { $ENV{PATH} = '/usr/ucb:/bin' } \& use Socket; \& use Carp; \& $EOL = "\e015\e012"; \& \& sub spawn; # de\*'claration pre\*'alable \& sub logmsg { print "$0 $$: @_ at ", scalar localtime, "\en" } \& \& my $port = shift || 2345; \& my $proto = getprotobyname('tcp'); \& $port = $1 if $port =~ /(\ed+)/; # nettoie le nume\*'ro de port \& \& socket(Server, PF_INET, SOCK_STREAM, $proto) || die "socket: $!"; \& setsockopt(Server, SOL_SOCKET, SO_REUSEADDR, \& pack("l", 1)) || die "setsockopt: $!"; \& bind(Server, sockaddr_in($port, INADDR_ANY)) || die "bind: $!"; \& listen(Server,SOMAXCONN) || die "listen: $!"; \& \& logmsg "server started on port $port"; \& \& my $waitedpid = 0; \& my $paddr; \& \& sub REAPER { \& $waitedpid = wait; \& $SIG{CHLD} = \e&REAPER; # abhorrons sysV \& logmsg "reaped $waitedpid" . ($? ? " with exit $?" : ''); \& } \& \& $SIG{CHLD} = \e&REAPER; \& \& for ( $waitedpid = 0; \& ($paddr = accept(Client,Server)) || $waitedpid; \& $waitedpid = 0, close Client) \& { \& next if $waitedpid and not $paddr; \& my($port,$iaddr) = sockaddr_in($paddr); \& my $name = gethostbyaddr($iaddr,AF_INET); \& \& logmsg "connection from $name [", \& inet_ntoa($iaddr), "] \& at port $port"; \& \& spawn sub { \& print "Hello there, $name, it's now ", scalar localtime, $EOL; \& exec '/usr/games/fortune' # XXX: `mauvais' terminateurs de ligne \& or confess "can't exec fortune: $!"; \& }; \& \& } \& \& sub spawn { \& my $coderef = shift; \& \& unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') { \& confess "usage: spawn CODEREF"; \& } \& \& my $pid; \& if (!defined($pid = fork)) { \& logmsg "cannot fork: $!"; \& return; \& } elsif ($pid) { \& logmsg "begat $pid"; \& return; # Je suis le pere \& } \& # ou bien je suis le fils \- on se multiplie \& \& open(STDIN, "<&Client") || die "can't dup client to stdin"; \& open(STDOUT, ">&Client") || die "can't dup client to stdout"; \& ## open(STDERR, ">&STDOUT") || die "can't dup stdout to stderr"; \& exit &$coderef(); \& } .Ve .PP Ce serveur prend la peine de cloner un processus fils via \fIfork()\fR pour chaque reque\*^te entrante. De cette fac\*,on, il peut re\*'pondre a\*` de nombreuses reque\*^tes en me\*^me temps, ce que vous pourriez ne pas toujours de\*'sirer. Me\*^me si vous n'utilisez pas \fIfork()\fR, le \fIlisten()\fR permettra de nombreuses connexions en cours. Les serveurs qui se dupliquent doivent prendre tout particulie\*`rement soin de nettoyer leurs enfants morts (appele\*'s \*(L"zombies\*(R" en jargon unixien), car autrement vous remplirez rapidement votre table des processus. .PP Nous vous sugge\*'rons d'utiliser l'option \fB\-T\fR pour profiter du taint checking (voir perlsec) me\*^me si vous ne tourner pas en setuid ou en setgid. C'est toujours une bonne ide\*'e pour les serveurs et les programmes exe\*'cute\*'s au nom de quelqu'un d'autre (comme les scripts \&\s-1CGI\s0), car cela diminue le risque que des personnes exte\*'rieures compromettent votre syste\*`me. .PP E\*'tudions un autre client \s-1TCP\s0. Celui-ci se connecte au service \s-1TCP\s0 \&\*(L"time\*(R" sur de nombreuses machines diffe\*'rentes et montre a\*` quel point leurs horloges diffe\*`rent du syste\*`me sur lequel il est exe\*'cute\*' : .PP .Vb 3 \& #!/usr/bin/perl \-w \& use strict; \& use Socket; \& \& my $SECS_of_70_YEARS = 2208988800; \& sub ctime { scalar localtime(shift) } \& \& my $iaddr = gethostbyname('localhost'); \& my $proto = getprotobyname('tcp'); \& my $port = getservbyname('time', 'tcp'); \& my $paddr = sockaddr_in(0, $iaddr); \& my($host); \& \& $| = 1; \& printf "%\-24s %8s %s\en", "localhost", 0, ctime(time()); \& \& foreach $host (@ARGV) { \& printf "%\-24s ", $host; \& my $hisiaddr = inet_aton($host) || die "unknown host"; \& my $hispaddr = sockaddr_in($port, $hisiaddr); \& socket(SOCKET, PF_INET, SOCK_STREAM, $proto) || die "socket: $!"; \& connect(SOCKET, $hispaddr) || die "bind: $!"; \& my $rtime = ' '; \& read(SOCKET, $rtime, 4); \& close(SOCKET); \& my $histime = unpack("N", $rtime) \- $SECS_of_70_YEARS ; \& printf "%8d %s\en", $histime \- time, ctime($histime); \& } .Ve .Sh "Clients et Serveurs \s-1TCP\s0 du Domaine Unix" .IX Subsection "Clients et Serveurs TCP du Domaine Unix" C'est bien pour les clients et les serveurs Internet, mais pour les communications locales ? Me\*^me si vous pouvez utiliser la me\*^me configuration, vous ne le voulez pas toujours. Les sockets du domaine Unix sont locales a\*` l'ho\*^te courant, et sont souvent utilise\*'es en interne pour imple\*'menter des tubes. Contrairement aux sockets du domaine Internet, les sockets Unix peuvent se voir dans le syste\*`me de fichier par \fIls\fR\|(1). .PP .Vb 2 \& % ls \-l /dev/log \& srw\-rw\-rw\- 1 root 0 Oct 31 07:23 /dev/log .Ve .PP Vous pouvez les tester avec le test de fichier \fB\-S\fR de Perl : .PP .Vb 3 \& unless ( \-S '/dev/log' ) { \& die "something's wicked with the print system"; \& } .Ve .PP Voici un exemple de client du domaine Unix : .PP .Vb 4 \& #!/usr/bin/perl \-w \& use Socket; \& use strict; \& my ($rendezvous, $line); \& \& $rendezvous = shift || '/tmp/catsock'; \& socket(SOCK, PF_UNIX, SOCK_STREAM, 0) || die "socket: $!"; \& connect(SOCK, sockaddr_un($rendezvous)) || die "connect: $!"; \& while (defined($line = )) { \& print $line; \& } \& exit; .Ve .PP Et voici un serveur correspondant. Vous n'avez pas besoin de vous soucier ici des stupides terminateurs de re\*'seau car les sockets Unix sont de fac\*,on certaine sur l'ho\*^te local (localhost, \s-1NDT\s0), et ainsi tout fonctionne bien. .PP .Vb 4 \& #!/usr/bin/perl \-Tw \& use strict; \& use Socket; \& use Carp; \& \& BEGIN { $ENV{PATH} = '/usr/ucb:/bin' } \& sub logmsg { print "$0 $$: @_ at ", scalar localtime, "\en" } \& \& my $NAME = '/tmp/catsock'; \& my $uaddr = sockaddr_un($NAME); \& my $proto = getprotobyname('tcp'); \& \& socket(Server,PF_UNIX,SOCK_STREAM,0) || die "socket: $!"; \& unlink($NAME); \& bind (Server, $uaddr) || die "bind: $!"; \& listen(Server,SOMAXCONN) || die "listen: $!"; \& \& logmsg "server started on $NAME"; \& \& my $waitedpid; \& \& sub REAPER { \& $waitedpid = wait; \& $SIG{CHLD} = \e&REAPER; # maudissez sysV \& logmsg "reaped $waitedpid" . ($? ? " with exit $?" : ''); \& } \& \& $SIG{CHLD} = \e&REAPER; \& \& for ( $waitedpid = 0; \& accept(Client,Server) || $waitedpid; \& $waitedpid = 0, close Client) \& { \& next if $waitedpid; \& logmsg "connection on $NAME"; \& spawn sub { \& print "Hello there, it's now ", scalar localtime, "\en"; \& exec '/usr/games/fortune' or die "can't exec fortune: $!"; \& }; \& } .Ve .PP Comme vous le voyez, il est remarquablement similaire au serveur \s-1TCP\s0 Internet, a\*` tel point que, en fait, nous avons omis plusieurs fonctions identiques \- \fIspawn()\fR, \fIlogmsg()\fR, \fIctime()\fR, et \s-1\fIREAPER\s0()\fR \- qui sont exactement les me\*^mes que dans l'autre serveur. .PP Alors pourquoi vouloir utiliser une socket du domaine Unix au lieu d'un tube nomme\*' plus simple ? Parce qu'un tube nomme\*' ne vous procure pas de sessions. Vous ne pouvez pas diffe\*'rencier les donne\*'es d'un processus de celle d'un autre. Avec la programmation de sockets, vous obtenez une session distincte pour chaque client : c'est pourquoi \&\fIaccept()\fR prend deux arguments. .PP Par exemple, supposons que vous voulez donner acce\*`s a\*` un de\*'mon serveur de base de donne\*'es tournant depuis longtemps a\*` des copains sur le web, mais seulement s'ils passent par une interface \s-1CGI\s0. Vous aurez un programme \s-1CGI\s0 petit et simple qui fera toutes les ve\*'rifications et les connexions que vous voudrez, puis agira comme un client du domaine Unix et se connectera a\*` votre serveur prive\*'. .SH "Clients TCP avec IO::Socket" .IX Header "Clients TCP avec IO::Socket" Pour ceux qui pre\*'fe\*`rent une interface de plus haut niveau pour la programmation des sockets, le module IO::Socket fournit une approche oriente\*'e objet. IO::Socket fait partie de la distribution standard de Perl a\*` partir de la version 5.004. Si vous exploitez une version pre\*'ce\*'dente de Perl, re\*'cupe\*'rez juste IO::Socket sur le \s-1CPAN\s0, ou\*` vous trouverez aussi des modules fournissant des interfaces simples pour les syste\*`mes suivants : \s-1DNS\s0, \s-1FTP\s0, Ident (\s-1RFC\s0 931), \s-1NIS\s0 et NISPlus, \&\s-1NNTP\s0, Ping, \s-1POP3\s0, \s-1SMTP\s0, \s-1SNMP\s0, SSLeay, Telnet, et Time \- pour n'en citer que quelques\-uns. .Sh "Un Client Simple" .IX Subsection "Un Client Simple" Voici un client qui cre\*'e une connexion \s-1TCP\s0 avec le service \*(L"daytime\*(R" sur le port 13 de l'ho\*^te appele\*' \*(L"localhost\*(R" et affiche tout ce que le serveur veut bien fournir. .PP .Vb 9 \& #!/usr/bin/perl \-w \& use IO::Socket; \& $remote = IO::Socket::INET\->new( \& Proto => "tcp", \& PeerAddr => "localhost", \& PeerPort => "daytime(13)", \& ) \& or die "cannot connect to daytime port at localhost"; \& while ( <$remote> ) { print } .Ve .PP Lorsque vous lancez ce programme, vous devriez obtenir quelque chose qui ressemble a\*` ceci : .PP .Vb 1 \& Wed May 14 08:40:46 MDT 1997 .Ve .PP Voici ce que signifient les parame\*`tres du constructeur \f(CW\*(C`new\*(C'\fR : .ie n .IP """Proto""" 4 .el .IP "\f(CWProto\fR" 4 .IX Item "Proto" C'est le protocole a\*` utiliser. Dans ce cas, le handle de socket retourne\*' sera connecte\*' a\*` une socket \s-1TCP\s0, car nous voulons une connexion oriente\*' flux (stream, \s-1NDT\s0), c'est\-a\*`\-dire une connexion qui se comporte assez comme un bon vieux fichier. Toutes les sockets ne sont pas de ce type. Par exemple, le protocole \s-1UDP\s0 peut e\*^tre utilise\*' pour cre\*'er une socket datagramme, pour transmettre des messages. .ie n .IP """PeerAddr""" 4 .el .IP "\f(CWPeerAddr\fR" 4 .IX Item "PeerAddr" C'est le nom ou l'adresse Internet de l'ho\*^te distant sur lequel le serveur tourne. Nous aurions pu spe\*'cifier un nom plus long comme \&\f(CW"www.perl.com"\fR, ou une adresse comme \f(CW"204.148.40.9"\fR. Pour les besoins de la de\*'monstration, nous avons utilise\*' le nom d'ho\*^te spe\*'cial \&\f(CW"localhost"\fR, qui doit toujours de\*'signer la machine sur laquelle le programme tourne. L'adresse Internet correspondant a\*` localhost est \&\f(CW"127.1"\fR, si vous pre\*'fe\*'rez l'utiliser. .ie n .IP """PeerPort""" 4 .el .IP "\f(CWPeerPort\fR" 4 .IX Item "PeerPort" C'est le nom du service ou le nume\*'ro de port auquel nous aimerions nous connecter. Nous aurions pu nous contenter d'utiliser juste \&\f(CW"daytime"\fR sur les syste\*`mes ayant un fichier de services syste\*`me bien configure\*', [\s-1FOOTNOTE:\s0 Le fichier de services syste\*`me est dans \&\fI/etc/services\fR sous Unix] mais juste au cas ou\*`, nous avons spe\*'cifie\*' le nume\*'ro de port (13) entre parenthe\*`ses. La simple utilisation du nume\*'ro aurait aussi fonctionne\*', mais les nume\*'ros constants rendent nerveux les programmeurs soigneux. .PP Vous avez remarque\*' comment la valeur de retour du constructeur \f(CW\*(C`new\*(C'\fR est utilise\*'e comme descripteur de fichier dans la boucle \f(CW\*(C`while\*(C'\fR ? C'est ce qu'on appelle un descripteur de fichier indirect, une variable scalaire contenant un descripteur de fichier. Vous pouvez l'utiliser de la me\*^me fac\*,on qu'un descripteur normal. Par exemple, vous pouvez y lire une ligne de la manie\*`re suivante : .PP .Vb 1 \& $line = <$handle>; .Ve .PP toutes les lignes restantes de cette fac\*,on : .PP .Vb 1 \& @lines = <$handle>; .Ve .PP et y e\*'crire une ligne ainsi : .PP .Vb 1 \& print $handle "some data\en"; .Ve .Sh "Un Client Webget" .IX Subsection "Un Client Webget" Voici un client simple qui prend en parame\*`tre un ho\*^te distant, puis une liste de documents a\*` re\*'cupe\*'rer sur cet ho\*^te. C'est un client plus inte\*'ressant que le pre\*'ce\*'dent car il commence par envoyer quelque chose avant de re\*'cupe\*'rer la re\*'ponse du serveur, .PP .Vb 10 \& #!/usr/bin/perl \-w \& use IO::Socket; \& unless (@ARGV > 1) { die "usage: $0 host document ..." } \& $host = shift(@ARGV); \& $EOL = "\e015\e012"; \& $BLANK = $EOL x 2; \& foreach $document ( @ARGV ) { \& $remote = IO::Socket::INET\->new( Proto => "tcp", \& PeerAddr => $host, \& PeerPort => "http(80)", \& ); \& unless ($remote) { die "cannot connect to http daemon on $host" } \& $remote\->autoflush(1); \& print $remote "GET $document HTTP/1.0" . $BLANK; \& while ( <$remote> ) { print } \& close $remote; \& } .Ve .PP le serveur web ge\*'rant le service \*(L"http\*(R", qui est suppose\*' e\*^tre a\*` son nume\*'ro de port standard, le 80. Si le serveur auquel vous essayez de vous connecter est a\*` un nume\*'ro de port diffe\*'rent (comme 1080 ou 8080), vous devez le spe\*'cifier par le parame\*`tre nomme\*' \f(CW\*(C`PeerPort => 8080\*(C'\fR. La me\*'thode \f(CW\*(C`autoflush\*(C'\fR est utilise\*'e sur la socket car autrement le syste\*`me placerait toutes les sorties que nous y envoyons dans un buffer (si vous e\*^tes sur un Mac, vous devrez aussi remplacer tous les \f(CW"\en"\fR dans votre code qui envoie des donne\*'es sur le re\*'seau par des \f(CW"\e015\e012"\fR.) .PP Se connecter au serveur est seulement la premie\*`re partie du travail : une fois que vous avez la connexion, vous devez utiliser le langage du serveur. Chaque serveur sur le re\*'seau a\*` son propre petit langage de commande, qu'il attend en entre\*'e. La chai\*^ne que nous envoyons au serveur et qui commence par \*(L"\s-1GET\s0\*(R" est dans la syntaxe \s-1HTTP\s0. Dans ce cas, nous demandons simplement chaque document spe\*'cifie\*'. Oui, nous cre\*'ons vraiment une nouvelle connexion pour chaque document, me\*^me s'ils se trouvent tous sur le me\*^me ho\*^te. Vous avez toujours parle\*' \s-1HTTP\s0 de cette fac\*,on. Les versions les plus re\*'centes des clients web peuvent demander au serveur distant de laisser la connexion ouverte un petit moment, mais le serveur n'est pas tenu d'honorer une telle reque\*^te. .PP Voici un exemple d'exe\*'cution de ce programme, que nous appellerons \&\fIwebget\fR : .PP .Vb 6 \& % webget www.perl.com /guanaco.html \& HTTP/1.1 404 File Not Found \& Date: Thu, 08 May 1997 18:02:32 GMT \& Server: Apache/1.2b6 \& Connection: close \& Content\-type: text/html \& \& 404 File Not Found \&

File Not Found

\& The requested URL /guanaco.html was not found on this server.

\& .Ve .PP Ok, ce n'est donc pas tre\*`s inte\*'ressant, car il n'a pas trouve\*' ce document en particulier. Mais une longue re\*'ponse n'aurait pas tenu sur cette page. .PP Pour avoir une version un peu plus de\*'veloppe\*'e de ce programme, vous devriez jeter un oeil sur le programme \fIlwp-request\fR inclus dans les modules \s-1LWP\s0 du \s-1CPAN\s0. .Sh "Client Interactif avec IO::Socket" .IX Subsection "Client Interactif avec IO::Socket" Bien, ceci est parfait si vous voulez envoyer une commande et obtenir une re\*'ponse, mais pourquoi ne pas mettre en place quelque chose de pleinement interactif, un peu a\*` la fac\*,on dont \fItelnet\fR fonctionne ? Ainsi vous pouvez taper une ligne, obtenir la re\*'ponse, taper une autre ligne, avoir la re\*'ponse, etc. .PP Ce client est plus complique\*' que les deux que nous avons e\*'crits jusqu'a\*` maintenant, mais si vous e\*^tes sur un syste\*`me qui supporte le puissant appel \f(CW\*(C`fork\*(C'\fR, la solution n'est pas si rude que c\*,a. Une fois que vous avez obtenu la connexion au service avec lequel vous de\*'sirez discuter, appelez \f(CW\*(C`fork\*(C'\fR pour cloner votre processus. Chacun de ces deux processus identiques a\*` un travail tre\*`s simple a\*` re\*'aliser : le parent copie tout ce qui provient de la socket sur la sortie standard, pendant que le fils copie simultane\*'ment sur la socket tout ce qui vient de l'entre\*'e standard. Accomplir la me\*^me chose en utilisant un seul processus serait \fIbien plus\fR difficile, car il est plus facile de coder deux processus qui font une seule chose, qu'un seul processus qui en fait deux (Ce principe de simplicite\*' est une pierre angulaire de la philosophie Unix, ainsi que du bon ge\*'nie logiciel, c'est probablement pour cela qu'il s'est e\*'tendu a\*` d'autres syste\*`mes). .PP Voici le code: .PP .Vb 4 \& #!/usr/bin/perl \-w \& use strict; \& use IO::Socket; \& my ($host, $port, $kidpid, $handle, $line); \& \& unless (@ARGV == 2) { die "usage: $0 host port" } \& ($host, $port) = @ARGV; \& \& # cree une connexion tcp a l'hote et sur le port specifie \& $handle = IO::Socket::INET\->new(Proto => "tcp", \& PeerAddr => $host, \& PeerPort => $port) \& or die "can't connect to port $port on $host: $!"; \& \& $handle\->autoflush(1); # pour que la sortie y aille tout de suite \& print STDERR "[Connected to $host:$port]\en"; \& \& # coupe le programme en deux processus, jumeaux identiques \& die "can't fork: $!" unless defined($kidpid = fork()); \& \& # le bloc if{} n'est traverse que dans le processus pere \& if ($kidpid) { \& # copie la socket sur la sortie standard \& while (defined ($line = <$handle>)) { \& print STDOUT $line; \& } \& kill("TERM", $kidpid); # envoie SIGTERM au fils \& } \& # le bloc else{} n'est traverse que dans le fils \& else { \& # copie l'entree standard sur la socket \& while (defined ($line = )) { \& print $handle $line; \& } \& } .Ve .PP La fonction \f(CW\*(C`kill\*(C'\fR dans le bloc \f(CW\*(C`if\*(C'\fR du pe\*`re est la\*` pour envoyer un signal a\*` notre processus fils (qui est dans le bloc \f(CW\*(C`else\*(C'\fR) de\*`s que le serveur distant a ferme\*' la connexion de son co\*^te\*'. .PP Si le serveur distant envoie les donne\*'es un octet a\*` la fois, et que vous avez besoin de ces donne\*'es imme\*'diatement sans devoir attendre une fin de ligne (qui peut tre\*`s bien ne jamais arriver), vous pourriez remplacer la boucle \f(CW\*(C`while\*(C'\fR dans le pe\*`re par ce qui suit : .PP .Vb 4 \& my $byte; \& while (sysread($handle, $byte, 1) == 1) { \& print STDOUT $byte; \& } .Ve .PP Faire un appel syste\*`me pour chaque octet que vous voulez lire n'est pas tre\*`s efficace (c'est le moins qu'on puisse dire) mais c'est le plus simple a\*` expliquer et cela fonctionne raisonnablement bien. .SH "Serveurs TCP avec IO::Socket" .IX Header "Serveurs TCP avec IO::Socket" Comme toujours, mettre en place un serveur est un peu plus exigeant que de faire tourner un client. Le mode\*`le est que le serveur cre\*'e un type de socket spe\*'cial qui ne fait rien a\*` part e\*'couter un port particulier pour attendre l'arrive\*'e d'une connexion. Il le fait en appelant la me\*'thode \f(CW\*(C`IO::Socket::INET\->new()\*(C'\fR avec des arguments le\*'ge\*`rement diffe\*'rents de ceux du client. .IP "Proto" 4 .IX Item "Proto" C'est le protocole a\*` utiliser. Comme pour nos clients, nous spe\*'cifierons toujours \f(CW"tcp"\fR ici. .IP "LocalPort" 4 .IX Item "LocalPort" Nous spe\*'cifions un port local dans l'argument \f(CW\*(C`LocalPort\*(C'\fR, ce que nous n'avons pas fait pour le client. C'est le nom du service ou le nume\*'ro du port dont vous voulez e\*^tre le serveur (sous Unix, les ports en dessous de 1024 sont re\*'serve\*'s au superutilisateur). Dans notre exemple, nous utiliserons le port 9000, mais vous pouvez utiliser n'importe quel port non utilise\*' couramment sur votre syste\*`me. Si vous essayez d'en utiliser un qui soit de\*'ja\*` exploite\*', vous obtiendrez le message \*(L"Address already in use\*(R". Sous Unix, la commande \f(CW\*(C`netstat \-a\*(C'\fR vous montrera quels services ont des serveurs en cours. .IP "Listen" 4 .IX Item "Listen" le parame\*`tre \f(CW\*(C`Listen\*(C'\fR de\*'termine le nombre maximal de connexion en cours que nous pouvons accepter avant de commencer a\*` rejeter les clients arrivants. Pensez-y comme a\*` une file d'attente pour vos appels te\*'le\*'phoniques. Le module Socket de bas niveau a un symbole spe\*'cial pour le maximum du syste\*`me, qui est \s-1SOMAXCONN\s0. .IP "Reuse" 4 .IX Item "Reuse" Le parame\*`tre \f(CW\*(C`Reuse\*(C'\fR est ne\*'cessaire pour que nous puissions rede\*'marrer notre serveur manuellement sans devoir attendre quelques minutes que les tampons du syste\*`me soient vide\*'s. .PP Une fois que la socket ge\*'ne\*'rique du serveur a e\*'te\*' cre\*'e\*'e en utilisant les parame\*`tres liste\*'s ci\-dessus, le serveur attend qu'un nouveau client s'y connecte. Le serveur se bloque dans la me\*'thode \f(CW\*(C`accept\*(C'\fR, qui devient finalement une connexion bidirectionnelle avec le client distant (faites un autoflush sur ce handle pour e\*'viter toute mise en tampon). .PP Pour e\*^tre plus gentil avec les utilisateurs, notre serveur leur offre un prompt pour qu'ils entrent leurs commandes. La plupart des serveurs ne font pas cela. A\*` cause du prompt sans caracte\*`re de nouvelle ligne, vous devrez utiliser la variante \f(CW\*(C`sysread\*(C'\fR du client interactif ci\-dessus. .PP Ce serveur accepte cinq commandes diffe\*'rentes, renvoyant la sortie au client. Notez que contrairement a\*` la plupart des serveurs en re\*'seau, celui-ci ne sait ge\*'rer qu'un seul client a\*` la fois. Les serveurs multithreads sont traite\*'s dans le chapitre 6 du Chameau (\*(L"Programmation en Perl\*(R", \s-1NDT\s0, avec un dromadaire sur la couverture). .PP Voici le code. .PP .Vb 3 \& #!/usr/bin/perl \-w \& use IO::Socket; \& use Net::hostent; # pour la version OO de gethostbyaddr \& \& $PORT = 9000; # choisir quelque chose qui n'est pas utilise \& \& $server = IO::Socket::INET\->new( Proto => 'tcp', \& LocalPort => $PORT, \& Listen => SOMAXCONN, \& Reuse => 1); \& \& die "can't setup server" unless $server; \& print "[Server $0 accepting clients]\en"; \& \& while ($client = $server\->accept()) { \& $client\->autoflush(1); \& print $client "Welcome to $0; type help for command list.\en"; \& $hostinfo = gethostbyaddr($client\->peeraddr); \& printf "[Connect from %s]\en", $hostinfo\->name || $client\->peerhost; \& print $client "Command? "; \& while ( <$client>) { \& next unless /\eS/; # ligne vide \& if (/quit|exit/i) { last; } \& elsif (/date|time/i) { printf $client "%s\en", scalar localtime; } \& elsif (/who/i ) { print $client `who 2>&1`; } \& elsif (/cookie/i ) { print $client `/usr/games/fortune 2>&1`; } \& elsif (/motd/i ) { print $client `cat /etc/motd 2>&1`; } \& else { \& print $client "Commands: quit date who cookie motd\en"; \& } \& } continue { \& print $client "Command? "; \& } \& close $client; \& } .Ve .SH "UDP : Transfert de Message" .IX Header "UDP : Transfert de Message" Un autre type de configuration client-server n'utilise pas de connexions, mais des messages. Les communications \s-1UDP\s0 ne\*'cessitent moins de ressources mais sont aussi moins fiables, car elles ne promettent pas du tout que les messages vont arriver, sans parler de leur ordre ou de l'e\*'tat dans lequel ils seront. Toutefois, l'\s-1UDP\s0 offre certains avantages sur \s-1TCP\s0, a\*` commencer par la capacite\*' a\*` faire des \&\*(L"broadcasts\*(R" ou des \*(L"multicasts\*(R" a\*` destination de tout un tas d'ho\*^tes d'un seul coup (habituellement sur votre sous\-re\*'seau local). Si vous e\*^tes tre\*`s inquiet sur la fiabilite\*' et commencez a\*` mettre en place des ve\*'rifications dans votre syste\*`me de messages, alors vous devriez probablement juste utiliser \s-1TCP\s0 pour commencer. .PP Voici un programme \s-1UDP\s0 similaire au client \s-1TCP\s0 Internet donne\*' pre\*'ce\*'demment. Toutefois, au lieu de contacter un ho\*^te a\*` la fois, la version \s-1UDP\s0 en contactera de nombreux de fac\*,on asynchrone en simulant un multicast puis en utilisant \fIselect()\fR pour attendre une activite\*' sur les E/S pendant dix secondes. Pour faire quelque chose de similaire en \&\s-1TCP\s0, vous seriez oblige\*' d'utiliser un handle de socket diffe\*'rent pour chaque ho\*^te. .PP .Vb 4 \& #!/usr/bin/perl \-w \& use strict; \& use Socket; \& use Sys::Hostname; \& \& my ( $count, $hisiaddr, $hispaddr, $histime, \& $host, $iaddr, $paddr, $port, $proto, \& $rin, $rout, $rtime, $SECS_of_70_YEARS); \& \& $SECS_of_70_YEARS = 2208988800; \& \& $iaddr = gethostbyname(hostname()); \& $proto = getprotobyname('udp'); \& $port = getservbyname('time', 'udp'); \& $paddr = sockaddr_in(0, $iaddr); # 0 means let kernel pick \& \& socket(SOCKET, PF_INET, SOCK_DGRAM, $proto) || die "socket: $!"; \& bind(SOCKET, $paddr) || die "bind: $!"; \& \& $| = 1; \& printf "%\-12s %8s %s\en", "localhost", 0, scalar localtime time; \& $count = 0; \& for $host (@ARGV) { \& $count++; \& $hisiaddr = inet_aton($host) || die "unknown host"; \& $hispaddr = sockaddr_in($port, $hisiaddr); \& defined(send(SOCKET, 0, 0, $hispaddr)) || die "send $host: $!"; \& } \& \& $rin = ''; \& vec($rin, fileno(SOCKET), 1) = 1; \& \& # timeout after 10.0 seconds \& while ($count && select($rout = $rin, undef, undef, 10.0)) { \& $rtime = ''; \& ($hispaddr = recv(SOCKET, $rtime, 4, 0)) || die "recv: $!"; \& ($port, $hisiaddr) = sockaddr_in($hispaddr); \& $host = gethostbyaddr($hisiaddr, AF_INET); \& $histime = unpack("N", $rtime) \- $SECS_of_70_YEARS ; \& printf "%\-12s ", $host; \& printf "%8d %s\en", $histime \- time, scalar localtime($histime); \& $count\-\-; \& } .Ve .SH "CIP du SysV" .IX Header "CIP du SysV" Me\*^me si la \s-1CIP\s0 du System V n'est pas aussi largement utilise\*'e que les sockets, elle a des usages inte\*'ressants. Vous ne pouvez pas, toutefois, utiliser efficacement la \s-1CIP\s0 du Syste\*`me V ou le \fImmap()\fR de Berkeley pour obtenir de la me\*'moire partage\*'e afin de partager une variable entre plusieurs processus. C'est parce que Perl re\*'allouerait votre chai\*^ne alors que vous ne le voulez pas. .PP Voici un petit exemple montrant l'utilisation de la me\*'moire partage\*'e. .PP .Vb 1 \& use IPC::SysV qw(IPC_PRIVATE IPC_RMID S_IRWXU); \& \& $size = 2000; \& $id = shmget(IPC_PRIVATE, $size, S_IRWXU) || die "$!"; \& print "shm key $id\en"; \& \& $message = "Message #1"; \& shmwrite($id, $message, 0, 60) || die "$!"; \& print "wrote: '$message'\en"; \& shmread($id, $buff, 0, 60) || die "$!"; \& print "read : '$buff'\en"; \& \& # le tampon de shmread est cadre a droite par des caracteres nuls. \& substr($buff, index($buff, "\e0")) = ''; \& print "un" unless $buff eq $message; \& print "swell\en"; \& \& print "deleting shm $key\en"; \& shmctl($id, IPC_RMID, 0) || die "$!"; .Ve .PP Voici un exemple de se\*'maphore : .PP .Vb 1 \& use IPC::SysV qw(IPC_CREAT); \& \& $IPC_KEY = 1234; \& $id = semget($IPC_KEY, 10, 0666 | IPC_CREAT ) || die "$!"; \& print "shm key $id\en"; .Ve .PP Mettez ce code dans un fichier se\*'pare\*' pour e\*^tre exe\*'cute\*' dans plus d'un processus. Appelez le fichier \fItake\fR : .PP .Vb 1 \& # creation d'un semaphore \& \& $IPC_KEY = 1234; \& $id = semget($IPC_KEY, 0 , 0 ); \& die if !defined($id); \& \& $semnum = 0; \& $semflag = 0; \& \& # semaphore 'take' \& # on attend que le semaphore soit a\*` zero \& $semop = 0; \& $opstring1 = pack("s!s!s!", $semnum, $semop, $semflag); \& \& # on incremente le compteur du semaphore \& $semop = 1; \& $opstring2 = pack("s!s!s!", $semnum, $semop, $semflag); \& $opstring = $opstring1 . $opstring2; \& \& semop($id,$opstring) || die "$!"; .Ve .PP Mettez ce code dans un fichier se\*'pare\*' pour e\*^tre exe\*'cute\*' dans plus d'un processus. Appelez ce fichier \fIgive\fR : .PP .Vb 3 \& # 'give' le semaphore \& # faites tourner ceci dans le processus originel, et vous verrez \& # que le second processus continue \& \& $IPC_KEY = 1234; \& $id = semget($IPC_KEY, 0, 0); \& die if !defined($id); \& \& $semnum = 0; \& $semflag = 0; \& \& # Decremente le compteur du semaphore \& $semop = \-1; \& $opstring = pack("s!s!s!", $semnum, $semop, $semflag); \& \& semop($id,$opstring) || die "$!"; .Ve .PP Le code de \s-1CIP\s0 SysV ci-dessus fut e\*'crit il y a longtemps, et il est de\*'finitivement de\*'glingue\*'. Pour un look plus moderne, voir le module IPC::SysV qui est inclus dans Perl a\*` partir de la version 5.005. .PP Un petit exemple de\*'montrant les files de messages SysV : .PP .Vb 1 \& use IPC::SysV qw(IPC_PRIVATE IPC_RMID IPC_CREAT S_IRWXU); \& \& my $id = msgget(IPC_PRIVATE, IPC_CREAT | S_IRWXU); \& \& my $sent = "message"; \& my $type = 1234; \& my $rcvd; \& my $type_rcvd; \& \& if (defined $id) { \& if (msgsnd($id, pack("l! a*", $type_sent, $sent), 0)) { \& if (msgrcv($id, $rcvd, 60, 0, 0)) { \& ($type_rcvd, $rcvd) = unpack("l! a*", $rcvd); \& if ($rcvd eq $sent) { \& print "okay\en"; \& } else { \& print "not okay\en"; \& } \& } else { \& die "# msgrcv failed\en"; \& } \& } else { \& die "# msgsnd failed\en"; \& } \& msgctl($id, IPC_RMID, 0) || die "# msgctl failed: $!\en"; \& } else { \& die "# msgget failed\en"; \& } .Ve .SH "NOTES" .IX Header "NOTES" La plupart de ces routines retournent silencieusement mais poliment \&\f(CW\*(C`undef\*(C'\fR lorsqu'elles e\*'chouent, au lieu de mourir sur-le-champ a\*` cause d'une exception non pie\*'ge\*'e (en ve\*'rite\*', certaines des nouvelles fonctions de conversion \fISocket\fR font un \fIcroak()\fR sur les arguments mal de\*'finis). Il est par conse\*'quent essentiel de ve\*'rifier les valeurs de retour de ces fonctions. De\*'butez toujours vos programmes utilisant des sockets de cette fac\*,on pour obtenir un succe\*`s optimal, et n'oubliez pas d'ajouter dans les serveurs l'option \fB\-T\fR de ve\*'rification de pollution dans la ligne #! : .PP .Vb 4 \& #!/usr/bin/perl \-Tw \& use strict; \& use sigtrap; \& use Socket; .Ve .SH "BUGS" .IX Header "BUGS" Toutes ces routines cre\*'ent des proble\*`mes de portabilite\*' spe\*'cifiques a\*` chaque syste\*`me. Comme il est note\*' par ailleurs, Perl est a\*` la merci de vos bibliothe\*`ques C pour la plus grande partie de comportement au niveau syste\*`me. Il est probablement plus su\*^r de conside\*'rer comme de\*'ficiente la se\*'mantique des signaux de SysV et de s'en tenir a\*` de simples ope\*'rations \s-1TCP\s0 et \s-1UDP\s0 sur les sockets ; e.g., n'essayez pas de passer des descripteurs de fichiers ouverts par une socket datagramme \&\s-1UDP\s0 locale si vous voulez que votre code ait une chance d'e\*^tre portable. .PP Comme il l'a e\*'te\*' mentionne\*' dans la section sur les signaux, puisque peu de fournisseurs offrent des bibliothe\*`ques C re\*'entrantes de fac\*,on su\*^re, le programmeur prudent fera peut de choses dans un handler a\*` part modifier la valeur d'une variable nume\*'rique pre\*'existante ; ou, s'il est bloque\*' dans un appel syste\*`me lent (rede\*'marrage), il utilisera \&\fIdie()\fR pour lever une exception et sortir par un \fIlongjmp\fR\|(3). En fait, me\*^me ceci peut dans certains cas provoquer un coredump. Il est probablement meilleur d'e\*'viter les signaux sauf lorsqu'ils sont absolument ine\*'vitables. Ce proble\*`me sera re\*'gle\*' dans une future version de Perl. .SH "VOIR AUSSI" .IX Header "VOIR AUSSI" La programmation d'applications en re\*'seau est bien plus vaste que ceci, mais cela devrait vous permettre de de\*'buter. .PP Pour les programmeurs intre\*'pides, le livre indispensable est \fIUnix Network Programming\fR par W. Richard Stevens (publie\*' chez Addison\-Wesley). Notez que la plupart des livres sur le re\*'seau s'adressent au programmeur C ; la traduction en Perl est laisse\*'e en exercice pour le lecteur. .PP La page de manuel \fIIO::Socket\fR\|(3) de\*'crit la bibliothe\*`que objet, et la page \fISocket\fR\|(3) l'interface de bas niveau vers les sockets. Au\-dela\*` des fonctions e\*'videntes dans perlfunc, vous devriez aussi jeter un oeil sur le fichier \fImodules\fR de votre miroir \s-1CPAN\s0 le plus proche (Voir perlmodlib ou mieux encore, la \fIPerl \s-1FAQ\s0\fR, pour une description de ce qu'est le \s-1CPAN\s0 et pour savoir ou\*` le trouver). .PP La section 5 du fichier \fImodules\fR est consacre\*'e au \*(L"Networking, Device Control (modems), and Interprocess Communication\*(R", et contient de nombreux modules en vrac, de nombreux modules concernant le re\*'seau, les ope\*'rations de Chat et d'Expect, la programmation \s-1CGI\s0, le \s-1DCE\s0, le \&\s-1FTP\s0, l'\s-1IPC\s0, \s-1NNTP\s0, les Proxy, Ptty, les \s-1RPC\s0, le \s-1SNMP\s0, le \s-1SMTP\s0, Telnet, les Threads, et ToolTalk \- pour n'en citer que quelques\-uns. .SH "AUTEUR" .IX Header "AUTEUR" Tom Christiansen, avec des vestiges occasionnels de la version originale de Larry Wall et des suggestions des porteurs de Perl. .SH "TRADUCTION" .IX Header "TRADUCTION" .Sh "Version" .IX Subsection "Version" Cette traduction franc\*,aise correspond a\*` la version anglaise distribue\*'e avec perl 5.6.0. Pour en savoir plus concernant ces traductions, consultez . .Sh "Traducteur" .IX Subsection "Traducteur" Roland Trique <\fIroland.trique@free.fr\fR> .PP Conseils\ : Guy Decoux <\fIdecoux@moulon.inra.fr\fR>, Guillaume van der Rest <\fIvanderrest@magnet.fsu.edu\fR> .Sh "Relecture" .IX Subsection "Relecture" Guy Decoux <\fIdecoux@moulon.inra.fr\fR>, Guillaume van der Rest <\fIvanderrest@magnet.fsu.edu\fR>