NOME
perlipc - Comunicazione Interprocesso in Perl (segnali, fifo, pipe, sottoprocessi robusti, socket e semafori)
DESCRIZIONE
Gli strumenti basilari di IPC [Inter-Process Communication, ossia Comunicazione Inter-Processo, NdT] in Perl sono concepiti a partire dai buoni, vecchi segnali Unix, dalle fifo, dalle aperture di pipe, dalle routine sui socket Berkeley e dalle chiamate IPC in SysV. Ciascuna delle quali viene utilizzata in situazioni leggermente differenti.
Segnali
Perl utilizza un modello di gestione dei segnali piuttosto semplice: l'hash %SIG contiene nomi, o riferimenti, di funzioni di gestione dei segnali installate dall'utente. Questi handler [dizione comune con cui si indicano queste funzioni di gestione dei segnali, NdT] verranno chiamati con un parametro di ingresso, costituito dal nome del segnale che l'ha generato. Un segnale può essere generato intenzionalmente da una particolare sequenza sulla tastiera, come ad esempio Control-C o Control-Z; può essere mandato da un altro processo; infine, può essere generato automaticamente dal kernel quando accadono eventi particolari, come l'uscita di un processo figlio, l'esaurimento dello stack da parte del vostro processo o il raggiungimento del limite di dimensione di un file.
Ad esempio, per catturare un segnale di interruzione, potete impostare un handler in questo modo:
sub cattura_modifica {
my $nomesegn = shift;
$fregature++;
die "Qualcuno mi ha inviato un SIG$nomesegn";
}
$SIG{INT} = 'cattura_modifica'; # potrebbe fallire nei moduli
$SIG{INT} = \&cattura_modifica; # strategia migliore
Prima della versione 5.7.3 di Perl, era necessario svolgere meno operazioni possibili all'interno di un handler; osservate come, nell'esempio, tutto quello che facciamo è impostare una variabile globale e poi sollevare un'eccezione. Questo dipende dal fatto che, nella maggior parte dei sistemi, le librerie non sono rientranti; in particolare, le routine di allocazione della memoria o di I/O non lo sono. Ciò significava che fare praticamente qualsiasi cosa nel vostri handler poteva in teoria generare una mancanza di memoria e conseguentemente un core dump - leggete "Segnali Differiti (Segnali Sicuri)" più avanti.
I nomi dei segnali sono quelli elencati dal comando kill -l
nel vostro sistema; in alternativa, potete prenderli dal modulo Config. Impostate una lista @signame
indicizzata per numero per prendere il nome, ed una tabella %signo
indicizzata per nome per avere il numero di segnale corrispondente:
use Config;
defined $Config{sig_name} || die "Nessun segnale?";
foreach $nome (split(' ', $Config{sig_name})) {
$signo{$name} = $i;
$signame[$i] = $nome;
$i++;
}
In questo modo, per controllare se il segnale 17 e SIGALRM coincidono, potete fare semplicemente:
print "segnale #17 = $signame[17]\n";
if ($signo{ALRM}) {
print "SIGALRM corrisponde al segnale $signo{ALRM}\n";
}
Potete anche scegliere di assegnare le stringhe 'IGNORE'
o 'DEFAULT'
come handler; in questo caso, Perl cercherà di scartare il segnale o di eseguire l'azione di default.
Sulla maggior parte delle piattaforme Unix, il segnale CHLD
(a volte noto come CLD
) ha un comportamento speciale rispetto all'impostazione di 'IGNORE'
. Impostando $SIG{CHLD}
a 'IGNORE'
su tali piattaforme, infatti, ha l'effetto di non creare processi zombie quando il processo padre si dimentica di eseguire wait()
sui propri processi figlio (ossia, i processi figli sono eliminati automaticamente). Chiamare wait()
con $SIG{CHLD}
impostato a 'IGNORE'
di solito restituisce -1
su tali piattaforme.
Alcuni segnali non possono essere né intercettati né ignorati, come KILL e STOP (ma non TSTP). Una strategia per ignorare temporaneamente i segnali consiste nell'utilizzo dell'istruzione local()
, che viene automaticamente ripristinata una volta che il vostro blocco termina. (Ricordatevi che i valori local()
-izzati sono ereditati anche dalle funzioni che vengono chiamate da dentro al blocco).
sub prezioso {
local $SIG{INT} = 'IGNORE';
&ulteriori_funzioni;
}
sub ulteriori_funzioni {
# le interruzioni sono ancora ignorate, per ora...
}
Inviare un segnale ad un processo di ID negativo significa che state mandando il segnale a tutto il gruppo di processi Unix. Il codice che segue invia il segnale di riattacca [hang-up, NdT] a tutti i processi nel gruppo corrente (ed imposta $SIG{HUP}
ad 'IGNORE'
per evitare di uccidere sé stesso):
{
local $SIG{HUP} = 'IGNORE';
kill HUP => -$$;
# un modo esotico per scrivere: kill('HUP', -$$)
}
Un altro segnale interessante da mandare è il numero zero. Questo segnale non ha un vero effetto su un processo figlio, ma controlla se questi è ancora vivo o ha cambiato il suo UID.
unless (kill 0 => $kid_pid) {
warn "qualcosa di maligno e` accaduto a $kid_pid";
}
Quando inviato ad un processo il cui UID non è identico a quello del processo che lo manda, il segnale numero zero può fallire perché vi mancano i permessi per mandare il segnale, anche se il processo è vivo. Potreste essere in grado di determinare la causa del fallimento utilizzando %!
:
unless (kill 0 => $pid or $!{EPERM}) {
warn "$pid sembra deceduto";
}
Potreste anche volere impiegare funzioni anonime per gli handler più semplici:
$SIG{INT} = sub { die "\nFuori di qui!\n" };
Questo, però, risulterebbe problematico per handler più complicati che hanno bisogno di re-installarsi. Poiché il meccanismo dei segnali di Perl è al momento basato sulla funzione signal(3)
della libreria C, potete talvolta essere così sfortunati da incorrere in sistemi dove tale funzione è "rotta", ossia in cui si comporta nel vecchio ed inaffidabile modo SysV invece che nel nuovo, più ragionevole approccio BSD e POSIX. Per questo motivo, vedrete che chi si mantiene sulla difensiva scrive handler in questo modo:
sub IL_TRISTO_MIETITORE {
$waitedpid = wait;
# detesto sysV: ci costringe non solo a reinstallare
# l'handler, ma anche a metterlo dopo la wait
$SIG{CHLD} = \&IL_TRISTO_MIETITORE;
}
$SIG{CHLD} = \&IL_TRISTO_MIETITORE;
# ora facciamo qualcosa che chiama fork...
o ancora meglio:
use POSIX ":sys_wait_h";
sub IL_TRISTO_MIETITORE {
my $child;
# Se un secondo figlio muore mentre siamo nell'handler causato
# dalla prima morte, non riceviamo un altro segnale. Per questo
# motivo, siamo costretti a fare un ciclo qui, altrimenti lasceremmo
# il figlio non cancellato come zombie. E la prossima volta che
# muoiono due processi figlio avremmo un altro zombie, e cosi`
# via.
while (($child = waitpid(-1,WNOHANG)) > 0) {
$Kid_Status{$child} = $?;
}
$SIG{CHLD} = \&IL_TRISTO_MIETITORE; # detesto sempre sysV
}
$SIG{CHLD} = \&IL_TRISTO_MIETITORE;
# ora facciamo qualcosa che chiama fork...
La gestione dei segnali è utilizzata, in Unix, anche per i timeout. Mentre siete al sicuro all'interno di un blocco eval{}
, impostate un handler per catturare i segnali di allarme, e poi impostatene uno ad un certo numero di secondi. Poi, provate ad effettuare la vostra operazione bloccante, cancellando l'allarme quando è terminata ma prima di uscire dal blocco eval{}
. Se va oltre il tempo stabilito, utilizzerete die()
per saltare fuori dal blocco, in maniera analoga a come potreste utilizzare longjmp()
o throw()
in altri linguaggi.
Ecco un esempio: eval { local $SIG{ALRM} = sub { die "alarm clock restart" }; alarm 10; flock(FH, 2); # lock sul write, bloccante alarm 0; }; if ($@ and $@ !~ /alarm clock restart/) { die }
Se l'operazione che va in timeout è sytem()
o qx()
, questa tecnica è soggetta alla generazione di zombie. Se la cosa vi preoccupa, avrete bisogno di mettere in piedi una vostra coppia di fork()
e exec()
, ed uccidere i processi figli erranti.
Per una gestione dei segnali più complessa, potreste voler dare un'occhiata al modulo standard POSIX. Con grosso rammarico, il modulo è quasi interamente non documentato, ma il file t/lib/posix.t dalla distribuzione sorgente di Perl contiene qualche esempio.
Gestione del segnale SIGHUP nei Demoni
Un processo che di solito parte quando il sistema viene inizializzato e che si chiude quando il sistema viene spento è detto demone [daemon in inglese, da Disk And Execution MONitor, ossia controllore di disco e di esecuzione. La traduzione italiana non consente ovviamente di mantenere l'acronimo, e si utilizza la traduzione letterale demone, NdT]. Se un processo demone ha un file di configurazione che viene modificato dopo che il processo è partito, dovrebbe esserci un modo per dirgli di rileggere tale file senza che, però, termini l'esecuzione del processo stesso. Molti demoni mettono a disposizione questo meccanismo mediante un handler opportuno del segnale SIGHUP
. Quando volete notificare al demone di rileggere il file, basta semplicemente che gli inviate il segnale SIGHUP
.
Non tutte le piattaforme reinstallano automaticamente i loro handler di segnale (nativi) dopo che un segnale è stato consegnato. Questo significa che l'handler lavora solo la prima volta che il segnale viene inviato. La soluzione a questo problema consiste nell'utilizzare handler POSIX
laddove disponibili, poiché il loro comportamento è ben definito.
Il seguente esempio implementa un semplice demone, che fa in modo di ripartire ogni volta che viene ricevuto il segnale SIGHUP
. Il codice vero e proprio è posto nella funzione codice()
, che stampa semplicemente qualche informazione di debug per mostrare che funziona e che dovrebbe essere riempita con un po' di vero codice.
#!/usr/bin/perl -w
use POSIX ();
use FindBin ();
use File::Basename ();
use File::Spec::Functions;
$|=1;
# rendiamo il demone multi-piattaforma, cosicche' exec chiami
# sempre lo script stesso con il giusto percorso, indipendentemente
# da come e` stato chiamato lo script.
my $script = File::Basename::basename($0);
my $SELF = catfile $FindBin::Bin, $script;
# POSIX elimina la maschera sigprocmask in maniera appropriata
my $sigset = POSIX::SigSet->new();
my $azione = POSIX::SigAction->new('sigHUP_handler',
$sigset,
&POSIX::SA_NODEFER);
POSIX::sigaction(&POSIX::SIGHUP, $azione);
sub sigHUP_handler {
print "ricevuto SIGHUP\n";
exec($SELF, @ARGV) or die "Impossibile ripartire: $!\n";
}
codice();
sub codice {
print "PID: $$\n";
print "ARGV: @ARGV\n";
my $c = 0;
while (++$c) {
sleep 2;
print "$c\n";
}
}
__END__
Pipe con nome
Una pipe con nome (spesso detta FIFO [questo sarà il termine che useremo da qui in poi, NdT]) è un vecchio meccanismo di IPC Unix per comunicazione fra processi sulla stessa macchina. Lavora proprio come regolari pipe anonime connesse fra loro, eccetto che i processi "si incontrano" attraverso un nome di file senza bisogno di essere parenti fra loro.
Per creare una FIFO utilizzate la funzione POSIX::mkfifo()
.
use POSIX qw( mkfifo );
mkfifo($percorso, 0700) or die "errore in mkfifo $percorso: $!";
Potete anche utilizzare il comando Unix mknod(1)
o, su alcuni sistemi, mkfifo(1)
. Potrebbe darsi che questi comandi non si trovino nel vostro PATH usuale.
# Il valore restituito da system e` in logica negata, per cui
# dobbiamo utilizzare && e non ||
#
$ENV{PATH} .= ":/etc:/usr/etc";
if ( system('mknod', $path, 'p')
&& system('mkfifo', $path) ) {
die "mk{nod,fifo} $path failed";
}
Una FIFO è conveniente quando volete connettere un processo ad un altro con il quale non ha una relazione di parentela. Quando aprite una FIFO, il programma si bloccherà finché non c'è qualcos'altro dall'altra parte.
Ad esempio, supponiamo che vogliate spedire il vostro file .signature [firma, NdT] in una FIFO che ha un programma Perl all'altro capo. Ora, ogni volta che un programma qualunque (come ad esempio un programma per l'invio di posta elettronica, un lettore di news, il programma finger
, ecc.) prova a leggere da quel file, tale programma in lettura si bloccherà ed il vostro programma fornirà la nuova firma. Utilizzeremo il test di verifica sulle pipe per stabilire se qualcuno (o qualcosa) ha inavvertitamente rimosso la nostra FIFO.
chdir; # ritorna a casa
$FIFO = '.signature';
while (1) {
unless (-p $FIFO) {
unlink $FIFO;
require POSIX;
POSIX::mkfifo($FIFO, 0700)
or die "errore mkfifo $FIFO: $!";
}
# la prossima riga blocca il processo finche' non
# arriva un lettore
open (FIFO, "> $FIFO") || die "impossibile scrivere su $FIFO: $!";
print FIFO "Tizio Caio (tizio\@example.org)\n", `fortune -s`;
close FIFO;
sleep 2; # per evitare duplicazione di segnali
Segnali Differiti (Segnali Sicuri)
Nelle versioni di Perl precedenti la 5.7.3, installare il codice Perl che tratta i segnali significava esporsi a pericoli di duplice natura. Prima di tutto, poche librerie di sistema sono rientranti. Se il segnale interrompe quando Perl sta eseguendo una funzione (come malloc(3)
o printf(3)
), ed il vostro $handler
poi chiama di nuovo la stessa funzione, potreste ottenere un comportamento non predicibile - spesso un core dum [salvataggio del nucleo centrale di un processo all'interno di un file]. Secondo, Perl stesso non è rientrante al suo livello più basso. Se il segnale interrompe Perl mentre sta cambiando le proprie strutture dati interne, in modo simile abbiamo che potrebbe risultare un comportamento non predicibile.
Sapendo tutto ciò, c'erano due cose che avreste potuto fare: essere paranoici o essere pragmatici. L'approccio paranoico consisteva nel fare il meno possibile all'interno dell'handler. Impostare un valore intero già esistente che ha già un valore e rientrare. Anche se quanto detto è un po' poco per il vero paranoico, che evita di utilizzare die
all'interno di un handler
perché il sistema sta lì quatto quatto pronto a darti la fregatura. L'approccio pragmatico consisteva nel dire "Conosco i rischi, ma preferisco la convenienza", di fare tutto quel che volevate all'interno dell'handler, e di essere preparati a ripulire i core dump una volta ogni tanto.
In Perl 5.7.3 e successivi, per evitare questi problemi i segnali sono "differiti" -- ossia quando il segnale viene consegnato al processo dal sistema (in particolare, al codice C che implementa Perl), viene impostato un flag e l'handler ritorna immediatamente. Successivamente, in punti strategicamente "sicuri" nell'interprete Perl (ad esempio, quando si sta per eseguire un nuovo opcode) vengono controllati i flag e viene eseguito l'handler a livello Perl da %SIG
. Lo schema "differito" consente molta più flessibilità nello scrivere gli handler di segnale perché sappiamo che l'interprete si trova in uno stato sicuro, e non all'interno di una funzione della libreria di sistema. Ad ogni modo, l'implementazione si discosta da quella dei Perl precedenti per i seguenti punti:
- Opcode che richiedono molto tempo
-
Poiché l'interprete controlla i flag di segnale solo quando sta per eseguire un nuovo opcode, se arriva un segnale durante un opcode particolarmente lungo (ad esempio, un'espressione regolare che opera su una stringa molto lunga) allora il segnale non verrà visto finché l'operazione non termina.
- IO Interrotto
-
Quando viene consegnato un segnale (ad esempio INT, con Control-C), il sistema operativo interrompe le operazioni di IO come
read
(utilizzata per implementare l'operatore Perl <>). Nelle versioni più vecchie di Perl l'handler era chiamato immediatamente (e poichéread
non è "insicura" il tutto funzionava bene). Con lo schema "differito" l'handler non viene chiamato subito, e se Perl sta utilizzandostdio
della libreria di sistema, la libreria stessa potrebbe ri-lanciare laread
senza restituire il controllo a Perl e dargli una possibilità di chiamare l'handler in%SIG
. Se nel vostro sistema accade questo, la soluzione consiste nell'utilizzare lo strato:perlio
per fare IO - almeno su quegli handle che volete che siano in grado di interrompersi con i segnali. (Lo strato:perlio
controlla i flag dei segnali e chiama gli handler in%SIG
prima di ripristinare le operazioni di IO).Da notare che, in Perl 5.7.3 e successivi, il comportamento di default è quello di utilizzare lo strato
:perlio
automaticamente.Osservate anche che alcune funzioni di libreria sul networking, come
gethostbyname()
, sono note per avere implementazioni di timeout proprie che possono collidere con i vostri timeout. Se state avendo problemi con queste funzioni, potete provare ad utilizzare la funzione POSIXsigaction()
, che ignora i segnali "sicuri" di Perl (notare che ciò significa sottoporsi volontariamente a possibili corruzioni in memoria, come descritto in precedenza). Invece di impostare$SIG{ALRM}
:local $SIG{ALRM} = sub { die "alarm" };
provate qualcosa tipo:
use POSIX qw(SIGALRM); POSIX::sigaction(SIGALRM, POSIX::SigAction->new(sub { die "alarm" })) or die "Errore nell'impostazione dell'handler per SIGALRM: $!\n";
- Chiamate di sistema riavviabili
-
Sui sistemi che lo supportano, le vecchie versioni di Perl utilizzavano il flag
SA_RESTART
nella fase di installazione degli handler%SIG
. Questo significava che le chiamate di sistema riavviabili avrebbero proseguito invece di terminare quando arrivava un segnale. Per consegnare i segnali differiti il prima possibile, Perl 5.7.3 e successivi non utilizzanoSA_RESTART
. Di conseguenza, le chiamate di sistema riavviabili possono fallire (con$!
impostato aEINTR
) laddove prima avrebbero avuto successo.Osservate che il layer di default
:perlio
riproverà a lanciareread
,write
eclose
come descritto in precedenza, e che le chiamatewait
ewaitpid
interrotte verranno sempre ritentate. - Segnali come "indicatori di fallimento"
-
Alcuni segnali, come ad esempio
SEGV
,ILL
eBUS
, vengono generati in conseguenza a fallimenti vari, come nella memoria virtuale. Di solito questi errori sono fatali, e c'è poco che un handler a livello Perl possa farci. (In particolare, il vecchio schema di gestione dei segnali era particolarmente poco sicuro in casi di questo genere). In ogni caso, se viene impostato un handler%SIG
per questi segnali il nuovo schema non fa altro che impostare un flag ed uscire, come descritto. Ciò può forzare il sistema operativo a ritentare l'istruzione che ha generato l'errore e - visto che non è cambiato niente - il segnale verrà generato di nuovo. Il risultato è un "ciclo" piuttosto bizzarro. In futuro, il meccanismo di gestione dei segnali in Perl potrebbe essere cambiato per evitare tutto ciò - possibilmente disabilitando semplicemente gli handler%SIG
su segnali di quel tipo. Fino ad allora, la soluzione consiste nell'evitare di impostare un handler in%SIG
per quei segnali. (Quali siano esattamente questi segnali dipende dal particolare sistema operativo). - Segnali generati dallo stato del sistema operativo
-
Su alcuni sistemi operativi, certi handler di segnale dovrebbero "fare qualcosa" prima di uscire. Un esempio può essere
CHLD
(anche noto comeCLD
in certi sistemi), che indica che un processo figlio è terminato. Su alcuni sistemi operativi si suppone che l'handler chiamiwait
per il processo figlio completato. Su tali sistemi lo schema dei segnali differiti non funzionerà per questi segnali (non effettua lawait
). Di nuovo, il fallimento apparirà come un ciclo perché il sistema operativo rigenererà il segnale, dal momento che ci sono processi figlio su cui non è stata chiamatawait
.
Se volete tornare al vecchio comportamento per la gestione dei segnali, senza curarvi delle possibili corruzioni di memoria, impostate la variabile di ambiente PERL_SIGNALS
su "unsafe"
(una nuova caratteristica introdotta da Perl 5.8.1).
Utilizzare open()
per IPC
L'istruzione Perl base open()
può essere utilizzata per effettuare comunicazioni interprocesso aggiungendo, o premettendo, un simbolo pipe [la sbarretta verticale "|", NdT] al secondo parametro di open()
. Eccome come lanciare qualcosa in un processo figlio verso il quale intendete scrivere:
open(SPOOLER, "| cat -v | lpr -h 2>/dev/null")
|| die "errore su fork: $!";
local $SIG{PIPE} = sub { die "s'e` rotta la pipe con lo spooler" };
print SPOOLER "blah blah blah\n";
close SPOOLER || die "errore in chiusura: $! $?";
Ed ecco come lanciare un processo figlio dal quale intendete leggere:
open(STATUS, "netstat -an 2>&1 |")
|| die "errore su fork: $!";
while (<STATUS>) {
next if /^(tcp|udp)/;
print;
}
close STATUS || die "errore in chiusura: $! $?";
Se siete sicuri che un particolare programma è uno script Perl che si aspetta di ricevere nomi di file in @ARGV
, il programmatore furbo può scrivere qualcosa del genere:
% programma f1 "comando1|" - f2 "comando2|" f3 < tmpfile
e, indipendentemente da quale tipo di shell viene chiamato, il programma Perl leggerà dal file f1, dal processo comando1, da standard input (ossia, tmpfile in questo caso), dal file f2, dal comando comando2 ed infine dal file f3. Carino eh?
Potreste osservare che è possibile utilizzare i backtick [le virgolette singole rovesciate "`", NdT] per ottenere un effetto pressoché uguale all'avere una pipe in lettura:
print grep { !/^(tcp|udp)/ } `netstat -an 2>&1`;
die "netstat fallito" if $?;
Se ciò è vero in superficie, è molto più efficiente lavorare una riga alla volta, perché non avete bisogno di caricare l'intero risultato in memoria tutto insieme. Vi dà anche un controllo più fine sull'intero processo, consentendovi di termiare il processo figlio prima della sua conclusione naturale, se volete.
State attenti a controllare i valori restituiti sia da open()
che da close()
. Se state scrivendo su una pipe, dovreste anche intercettare SIGPIPE
. Pensate a cosa succederebbe, altrimenti, quando lanciate una pipe verso un comando che non esiste: la open()
avrà molto probabilmente successo (poiché riflette il successo della fork()
), ma poi la vostra uscita fallirà -- in maniera spettacolare. Perl non può sapere se il comando ha funzionato, perché in realtà questo viene eseguito in un processo separato in cui exec()
potrebbe essere fallita. Per questo motivo, mentre chi legge da comandi fasulli si vede restituire solo una "fine file" veloce, chi scrive verso tali comandi si vedrà recapitare un segnale che è meglio essere pronti a gestire. Considerate:
open(FH, "|fasullo") or die "errore nella fork: $!";
print FH "bang\n" or die "errore nella write: $!";
close FH or die "errore nella close: $!";
Questo programma non esploderà fino alla close
, e scoppierà con un SIGPIPE
. Per catturarlo, potreste utilizzare:
$SIG{PIPE} = 'IGNORE';
open(FH, "|fasullo") or die "errore nella fork: $!";
print FH "bang\n" or die "errore nella write: $!";
close FH or die "errore nella close: $!";
Filehandle
Sia il proceso principale che qualunque processo figlio esso generi, condividono gli stessi filehandle STDIN
, STDOUT
e STDERR
. Se entrambi i processi tentano di accedervi allo stesso momento, potrebbero succedere strane cose. Potreste inoltre voler chiudere e riaprire i filehandle per il processo figlio. Potete aggirare questo problema aprendo la pipe con open()
, ma su alcuni sistemi questo significa che il processo figlio non può sopravvivere al processo padre.
Processi in Background
[Background sta per "dietro le quinte", "sullo sfondo", e serve a descrivere processi che possono girare senza bisogno di interagire con l'utente, NdT]
Potete lanciare un comando in background con:
system("cmd &");
I filehandle STDOUT
e STDERR
(insieme, forse, a STDIN
, dipendentemente dalla vostra shell) saranno gli stessi del processo padre. Non avrete bisogno di intercettare SIGCHLD
a causa del fatto che ha luogo una doppia fork()
(guardate più avanti per maggiori dettagli).
Dissociazione Completa del Figlio dal Padre
In alcuni casi (per lanciare dei processi serventi, ad esempio) avrete bisogno di dissociare completamente il processo figlio dal padre; questo processo è spesso chiamato "demonizzazione". Un demone che si comporti a modo si sposterà anche nella directory radice con chdir()
(in modo da non bloccare un eventuale unmount
del filesystem contenente la directory da dove è stato lanciato) e redirigerà i propri descrittori di file standard da e su /dev/null (in modo che un output casuale non andrà a finire sul terminale dell'utente).
use POSIX 'setsid';
sub demonizza {
chdir '/' or die "Impossibile chdir in /: $!";
open STDIN, '/dev/null' or die "Impossibile leggere /dev/null: $!";
open STDOUT, '>/dev/null'
or die "Impossibile scrivere su /dev/null: $!";
defined(my $pid = fork) or die "Errore su fork: $!";
exit if $pid;
setsid or die "Impossibile lanciare una nuova sessione: $!";
open STDERR, '>&STDOUT' or die "Errore su dup di stdout: $!";
}
La chiamata a fork()
deve essere effettuata prima di setsid()
in modo da assicurare che non siate leader del gruppo di processi (in tal caso setsid()
fallirebbe). Se il vostro sistema non ha setsid()
, aprite /dev/tty ed utilizzate la ioctl()
TIOCNOTTY
su di esso. Consultate tty(4) per i dettagli.
Gli utenti non-Unix dovrebbero controllare il modulo ProprioSistemaOperativo::Process per altre soluzioni.
Aperture di Pipe Sicure
Un altro approccio interessante alla IPC consiste nel trasformare il vostro programma singolo in uno multiprocesso e comunicare fra (o a volte contro) i vari processi. La funzione open()
accetta un argomento come file che può essere o "-|"
o "|-"
per fare una cosa molto interessante: genera un processo figlio connesso al filehandle che state aprendo. Il figlio esegue lo stesso programma del padre. Ciò risulta utile per aprire un file in maniera sicura sotto una determinata coppia UID o GID, ad esempio. Se aprite una pipe verso "meno", potete scrivere sul filehandle che state aprendo ed il processo figlio se lo troverà nel proprio STDIN
. Se aprite una pipe da "meno", invece, potete leggere dal filehandle aperto qualsiasi cosa il processo figlio scriva sul proprio STDOUT
.
use English '-no_match_vars';
my $contatore_sleep = 0;
do {
$pid = open(FIGLIO_CUI_SCRIVERE, "|-");
unless (defined $pid) {
warn "errore fork: $!";
die "ci rinuncio" if $contatore_sleep++ > 6;
sleep 10;
}
} until defined $pid;
if ($pid) { # padre
print FIGLIO_CUI_SCRIVERE @some_data;
close(FIGLIO_CUI_SCRIVERE) || warn "il figlio ha restituito $?";
} else { # figlio
($EUID, $EGID) = ($UID, $GID); # solo programmi suid
open (FILE, "> /file/sicuro")
|| die "errore open() per /file/sicuro: $!";
while (<STDIN>) {
print FILE; # Lo STDIN del figlio e` FIGLIO_CUI_SCRIVERE nel padre
}
exit; # non dimenticatevelo
}
Un altro comune utilizzo per questo costrutto si ha quando avete bisogno di eseguire qualcosa senza che la shell interferisca. Con system()
sarebbe immediato, ma non potete utilizzare una open()
di una pipe o i backtick in maniera sicura. Questo si ha perché non c'è modo di impedire alla shell di mettere le proprie mani sui vostri argomenti. Utilizzate invece il controllo di basso livello su exec()
direttamente.
Ecco un backtick o un'apertura di pipe sicuri per la lettura:
# aggiungere la gestione degli errori come sopra
$pid = open(FIGLIO_DA_LEGGERE, "-|");
if ($pid) { # padre
while (<FIGLIO_DA_LEGGERE>) {
# fare qualcosa di interessante qui
}
close(FIGLIO_DA_LEGGERE) || warn "il figlio ha restituito $?";
} else { # figlio
($EUID, $EGID) = ($UID, $GID); # solo suid
exec($program, @options, @args)
|| die "errore in exec: $!";
# PARTE NON RAGGIUNTA
}
Ed ecco un'apertura di pipe sicura per la scrittura:
# aggiungere la gestione degli errori come sopra
$pid = open(FIGLIO_CUI_SCRIVERE, "|-");
$SIG{PIPE} = sub { die "oops, s'e` rotta la pipe di $program" };
if ($pid) { # padre
for (@data) {
print FIGLIO_CUI_SCRIVERE;
}
close(FIGLIO_CUI_SCRIVERE) || warn "il figlio ha restituito $?";
} else { # figlio
($EUID, $EGID) = ($UID, $GID);
exec($program, @options, @args)
|| die "errore in exec: $!";
# PARTE NON RAGGIUNTA
}
A partire dalla versione 5.8.0 di perl, potete anche utilizzare la forma di lista di open()
per le pipe; la sintassi:
open FIGLIO_PS, "-|", "ps", "aux" or die $!;
effettua un fork del comando ps(1) (senza lanciare a sua volta una shell, visto che ci sono più di tre argomenti per open()
), e legge il relativo output standard utilizzando il filehandle FIGLIO_PS
. È implementata anche la sintassi corrispondente per scrivere su pipe di comandi (con "|-"
al posto di "-|"
).
Osservate che queste operazioni sono delle fork()
Unix complete, il che significa che potrebbero non essere implementate correttamente su altri sistemi. In aggiunta, non sono esattamente multithreading. Se volete imparare qualcosa di più sul threading, consultate i moduli citati di seguito nella sezione "SI VEDA ANCHE".
Comunicazione Bidirezionale con un Altro Processo
Mentre tutto ciò funziona ragionevolmente bene per comunicazioni unidirezionali, come realizziamo una comunicazione bidirezionale? In realtà, la cosa ovvia che vi piacerebbe fare non funziona:
open(PROG_PER_LEGGERE_E_SCRIVERE, "| some program |")
e se vi dimenticate di usare la direttiva use warnings
o il parametro -w, vi perderete del tutto il messaggio diagnostico:
Can't do bidirectional pipe at -e line 1.
[Impossibile effettuare una pipe bidirezionale in -e riga 1, NdT].
Se volete veramente farlo, potete utilizzare la funzione di libreria standard open2()
per cattuare entrambe le estremità. Esiste anche una open3()
per I/O tridirezionale, in modo da catturare anche lo STDERR
del processo figlio, ma in questo modo dovreste utilizzare un loop contorto con select()
e non vi consentirebbe di utilizzare le normali operazioni di input di Perl.
Se date un'occhiata al sorgente, noterete che open2()
utilizza primitive di basso livello come pipe()
Unix e chiamate ad exec()
per creare tutte le connessioni. Mentre avrebbe potuto essere leggermente più efficiente utilizzare socketpair()
, sarebbe stato meno portabile di quanto non è in realtà. Le funzioni open2()
e open3()
con buona probabilità non funzioneranno se non su un sistema Unix o su qualche altro che sia aderente a POSIX.
Ecco un esempio di utilizzo di open2()
:
use FileHandle;
use IPC::Open2;
$pid = open2(*Lettore, *Scrittore, "cat -u -n" );
print Scrittore "stuff\n";
$preso = <Lettore>;
Il problema qui è che la bufferizzazione Unix vi rovinerà veramente la giornata. Anche se il vostro filehandle Scrittore
si libera automaticamente [auto-flushed, NdT], ed il processo dall'altra parte raccoglierà i vostri dati tempestivamente, non potete di norma fare niente per forzare quest'ultimo a restituirvi dati indietro, in una maniera similmente veloce. In questo caso potremmo, perché abbiamo passato l'opzione -u al programma cat
in modo da renderlo non bufferizzato. Ma ben pochi comandi Unix sono progettati per operare su pipe, per cui questo approccio funziona raramente a meno che non abbiate scritto di vostro pugno il programma dall'altro lato di questa coppia di pipe a doppia via.
Una soluzione a questo problema risiede nella libreria non standard Comm.pl. Questa utilizza pseudo-tty per indurre il vostro programma a comportarsi in maniera più ragionevole:
require 'Comm.pl';
$ph = open_proc('cat -n');
for (1..10) {
print $ph "una riga\n";
print "ho ricevuto ", scalar <$ph>;
}
In questo modo, non siete obbligati ad avere il controllo sul codice sorgente del programma che state utilizzando. La libreria Comm
contiene anche le funzioni expect()
e interact()
. Cercate la libreria (e speriamo il suo successore IPC::Chat) nell'archivio CPAN più vicino, come spiegato nella sezione SI VEDA ANCHE
più avanti.
Il modulo più nuovo Expect.pm su CPAN punta a risolvere questo tipo di problemi. Esso richiede altri due moduli da CPAN: IO::Pty e IO::Stty. Imposta uno pseudo-terminal per interagire con quei programmi che insistono sul voler parlare con il driver del dispositivo terminale. Se il vostro sistema compare fra quelli supportati, questa potrebbe essere la vostra puntata migliore.
Comunicazione Bidirezionale con Voi Stessi
Se volete, potete effettuare una chiamata di basso livello a pipe()
e fork()
per metterle insieme con le vostre mani. L'esempio che segue parla da sé, ma potreste riaprire i filehandle appropriati verso STDIN
e STDOUT
e chiamare altri processi.
#!/usr/bin/perl -w
# pipe1 - comunicazione bidirezionale utilizzando due coppie di pipe
# progettato per chi non ha socketpair
use IO::Handle; # migliaia di righe di codice solo per autoflush :-(
pipe(PADRE_LET, FIGLIO_SCR); # XXX: fallimento?
pipe(FIGLIO_LET, PADRE_SCR); # XXX: fallimento?
FIGLIO_SCR->autoflush(1);
PADRE_SCR->autoflush(1);
if ($pid = fork) {
close PADRE_LET; close PADRE_SCR;
print FIGLIO_SCR "Pid Padre $$ sta inviando questo\n";
chomp($riga = <FIGLIO_LET>);
print "Pid Padre $$ ha appena letto questo: `$riga'\n";
close FIGLIO_LET; close FIGLIO_SCR;
waitpid($pid,0);
} else {
die "errore fork: $!" unless defined $pid;
close FIGLIO_LET; close FIGLIO_SCR;
chomp($riga = <PADRE_LET>);
print "Pid Figlio $$ ha appena letto questo: `$line'\n";
print PADRE_SCR "Pid Figlio $$ sta inviando questo\n";
close PADRE_LET; close PADRE_SCR;
exit;
}
In realtà, non dovete effettuare esattamente due chiamate a pipe()
. Se avete la chiamata di sistema socketpair()
, essa farà tutto questo al posto vostro.
#!/usr/bin/perl -w
# pipe2 - comunicazione bidirezionale con socketpair
# "i migliori vanno sempre in entrambe le strade"
use Socket;
use IO::Handle; # migliaia di righe solo per autoflush :-(
# Utilizziamo AF_UNIX perchE<eacute> sebbene *_LOCAL sia la forma
# POSIX 1003.1g della costante, molte macchine ancora non l'hanno.
socketpair(FIGLIO, PADRE, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
or die "socketpair: $!";
FIGLIO->autoflush(1);
PADRE->autoflush(1);
if ($pid = fork) {
close PADRE;
print FIGLIO "Pid Padre $$ sta mandando questo\n";
chomp($riga = <FIGLIO>);
print "Pid Padre $$ ha appena letto questo: `$line'\n";
close FIGLIO;
waitpid($pid,0);
} else {
die "errore fork: $!" unless defined $pid;
close FIGLIO;
chomp($riga = <PADRE>);
print "Pid Figlio $$ ha appena letto questo: `$line'\n";
print PADRE "Pid Figlio $$ sta mandando questo\n";
close PADRE;
exit;
}
Socket: Comunicazione Client/Server
Nonostante non siano limitati ai sistemi operativi derivati da Unix (ad esempio, WinSock sui PC dà supporto ai socket, così come alcune librerie su VMS), potreste non avere i socket sul vostro sistema, nel qual caso questa sezione non sarà in grado di darvi molto. Con i socket, potete realizzare sia circuiti virtuali (ossia, flussi TCP) che datagrammi (ossia, pacchetti UDP). Potreste perfino essere in grado di fare di più, ma questo dipende dal vostro sistema.
Le funzioni Perl per trattare i socket hanno gli stessi nomi delle corrispondenti chiamate di sistema in C, ma i relativi argomenti tendono a differire per due ragioni: prima di tutto, i filehandle Perl funzionano differentemente dai descrittori di file in C; secondo, Perl conosce già la lunghezza delle stringhe con cui ha a che fare, per cui non avete bisogno di passare questa informazione.
Uno dei maggiori problemi con qualche vecchio codice Perl sui socket era che venivano utilizzati dei valori fissati esplicitamente nel codice per alcune costanti, il che incide in maniera molto negativa sulla portabilità. Se vi capita di vedere codice che fa cose come impostare esplicitamente $AF_INET = 2
, sappiate che siete in guai grossi: un approccio immensamente superiore consiste nell'utilizzare il modulo Socket
, che dà un accesso molto più affidabile alle varie costanti e funzioni di cui potete avere bisogno.
Se non state scrivendo una coppia server/client per un protocollo esistente come NNTP o SMTP, dovreste pensare accuratamente a come il vostro server verrà a conoscenza che il client ha finito di parlare, e vice versa. La maggior parte dei protocolli sono basati su messaggi e risposte su una singola riga (di modo che l'altra parte sa che il trasmittente ha finito di parlare quando riceve un carattere "\n"), oppure con messaggi e risposte su righe multiple, ma che terminano con un punto su una riga vuota (ossia, "\n.\n" termina un messaggio o una risposta).
Terminatori di Riga in Internet
Il terminatore di riga in Internet è "\015\012". In alcune varianti ASCII di Unix, questa sequenza può di norma scriversi anche "\r\n" ma, sotto altri sistemi, "\r\n" potrebbe a volte diventare "\015\015\012", "\012\012\015" o qualcosa di ancora differente. Gli standard specificano che scrivere "\015\012" è conforme ("siate fiscali in quello che fornite..."), ma raccomandano anche di accettare uno "\012" da solo in ingresso ("... ma siate pazienti con ciò che ricevete."). Non siamo stati sempre bravissimi su tutto ciò nel codice di questo documento, ma non dovreste trovarvi male, a meno che non siate su un Mac.
Client e Server TCP nel Dominio Internet
Utilizzate socket del dominio Internet quando volete realizzare una comunicazione client-server che potrebbe estendersi a macchine al di fuori del vostro sistema.
Ecco un esempio di client TCP che utilizza i socket del dominio Internet:
#!/usr/bin/perl -w
use strict;
use Socket;
my ($remoto,$porta, $iaddr, $paddr, $proto, $riga);
$remoto = shift || 'localhost';
$porta = shift || 2345; # una porta a caso
if ($porta =~ /\D/) { $porta = getservbyname($porta, 'tcp') }
die "Nessuna porta" unless $porta;
$iaddr = inet_aton($remoto) || die "nessun host: $remoto";
$paddr = sockaddr_in($porta, $iaddr);
$proto = getprotobyname('tcp');
socket(SOCK, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
connect(SOCK, $paddr) || die "connect: $!";
while (defined($riga = <SOCK>)) {
print $riga;
}
close (SOCK) || die "close: $!";
exit;
Ed ecco un server conforme, di modo che funzioni. Lasceremo l'indirizzo pari a INADDR_ANY
, in modo che il kernel possa scegliere l'interfaccia più appropriata su host con più interfacce. Se volete agganciarvi ad un'interfaccia in particolare (come, ad esempio, l'interfaccia esterna di una macchina che funge da gateway firewall), dovreste inserire tale indirizzo reale.
#!/usr/bin/perl -Tw
use strict;
BEGIN { $ENV{PATH} = '/usr/ucb:/bin' }
use Socket;
use Carp;
my $EOL = "\015\012";
sub logmsg { print "$0 $$: @_ at ", scalar localtime, "\n" }
my $porta = shift || 2345;
my $proto = getprotobyname('tcp');
($porta) = $porta =~ /^(\d+)$/ or die "porta non valida";
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 partito sulla porta $porta";
my $paddr;
$SIG{CHLD} = \&REAPER;
for ( ; $paddr = accept(Client,Server); close Client) {
my($porta,$iaddr) = sockaddr_in($paddr);
my $nome = gethostbyaddr($iaddr,AF_INET);
logmsg "connessione da $nome [",
inet_ntoa($iaddr), "] alla porta $porta";
print Client "Ehila', $nome, ora siamo alle ",
scalar localtime, $EOL;
}
Ed ecco una versione con thread multipli. È multithread nel senso che come la maggior parte dei server tipici, fa partire (genera) un processo di servizio per gestire una richiesa di un client, in modo che il server principale possa tornare subito ad ascoltare richieste da un nuovo client.
#!/usr/bin/perl -Tw
use strict;
BEGIN { $ENV{PATH} = '/usr/ucb:/bin' }
use Socket;
use Carp;
my $EOL = "\015\012";
sub spawn; # dichiarazione anticipata
sub logmsg { print "$0 $$: @_ at ", scalar localtime, "\n" }
my $porta = shift || 2345;
my $proto = getprotobyname('tcp');
($porta) = $porta =~ /^(\d+)$/ or die "porta non valida";
socket(Server, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
setsockopt(Server, SOL_SOCKET, SO_REUSEADDR,
pack("l", 1)) || die "setsockopt: $!";
bind(Server, sockaddr_in($porta, INADDR_ANY)) || die "bind: $!";
listen(Server,SOMAXCONN) || die "listen: $!";
logmsg "servizio partito sulla porta $porta";
my $waitedpid = 0;
my $paddr;
use POSIX ":sys_wait_h";
sub REAPER {
my $child;
while (($waitedpid = waitpid(-1,WNOHANG)) > 0) {
logmsg "eliminato $waitedpid" .
($? ? " con codice di uscita $?" : '');
}
$SIG{CHLD} = \&REAPER; # odiate sysV
}
$SIG{CHLD} = \&REAPER;
for ( $waitedpid = 0;
($paddr = accept(Client,Server)) || $waitedpid;
$waitedpid = 0, close Client)
{
next if $waitedpid and not $paddr;
my($porta,$iaddr) = sockaddr_in($paddr);
my $nome = gethostbyaddr($iaddr,AF_INET);
logmsg "connessione da $nome [",
inet_ntoa($iaddr), "] alla porta $porta";
spawn sub {
$|=1;
print "Ehila', $nome, siamo alle ", scalar localtime, $EOL;
exec '/usr/games/fortune' # XXX: terminatori di linea `sbagliati'
or confess "errore su exec fortune: $!";
};
}
sub spawn {
my $coderef = shift;
unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') {
confess "utilizzo: spawn RIFERIMENTO_A_CODICE";
}
my $pid;
if (!defined($pid = fork)) {
logmsg "errore fork: $!";
return;
} elsif ($pid) {
logmsg "lanciato $pid";
return; # Sono il processo padre
}
# altrimenti sono il figlio, lancia il codice passato
open(STDIN, "<&Client") || die "errore dup client su stdin";
open(STDOUT, ">&Client") || die "errore dup client su stdout";
## open(STDERR, ">&STDOUT") || die "errore dup stdout su stderr";
exit &$coderef();
}
Questo server si prende la briga di lanciare una versione clone figlia utilizzando fork()
per ogni richiesta in arrivo. In questo modo può gestire molte richieste allo stesso tempo, ma potrebbe non essere il comportamento che desiderate in ogni momento. Anche se non chiamate fork()
, la funzione listen()
vi consentirà di avere molte connessioni pendenti. Usare fork()
per generare i processi serventi deve essere fatto con particolare cautela nella ripulitutra dei figli deceduti (anche detti "zombie" nel gergo Unix), perché altrimenti riempirete rapidamente la vostra tabella dei processi.
Vi consigliamo di utilizzare l'opzione -T per attivare il controllo taint [controllo di marcatura per dati inattendibili, NdT], anche se il processo non sono in esecuzione setuid
o setgid
. È sempre una buona idea farlo per server ed altri programmi che sono eseguiti per conto di qualcun altro (come gli script CGI), perché riduce le eventualità che persone dall'esterno siano in grado di compromettere il vostro sistema.
Analizziamo ora un altro client TCP. Questo si connette al servizio TCP "time" su un certo numero di macchine differenti, e mostra quanto i loro orologi di sistema differiscono da quello della macchina su cui viene eseguito:
#!/usr/bin/perl -w
use strict;
use Socket;
my $SECONDI_IN_70_ANNI = 2208988800;
sub ctime { scalar localtime(shift) }
my $iaddr = gethostbyname('localhost');
my $proto = getprotobyname('tcp');
my $porta = getservbyname('time', 'tcp');
my $paddr = sockaddr_in(0, $iaddr);
my($host);
$| = 1;
printf "%-24s %8s %s\n", "localhost", 0, ctime(time());
foreach $host (@ARGV) {
printf "%-24s ", $host;
my $hisiaddr = inet_aton($host) || die "host sconosciuto";
my $hispaddr = sockaddr_in($porta, $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) - $SECONDI_IN_70_ANNI;
printf "%8d %s\n", $histime - time, ctime($histime);
}
Client e Server TCP nel Dominio Unix
Tutto ciò va bene per client e server nel dominio Internet, ma cosa possiamo dire delle comunicazioni locali? Anche se potete utilizzare lo stesso meccanismo, a volte potreste preferire non farlo. I socket del dominio Unix sono locali all'host corrente, e sono spesso utilizzati internamente per realizzare le pipe. Diversamente dai socket del dominio Internet, i socket del dominio Unix possono essere visualizzati nel file system con un listato di ls(1)
.
% ls -l /dev/log
srw-rw-rw- 1 root 0 Oct 31 07:23 /dev/log
Potete provare cosa sono con il test per i file di Perl -S:
unless ( -S '/dev/log' ) {
die "c'e` qualcosa di marcio nel sistema di log";
}
Ecco un client del dominio Unix di esempio:
#!/usr/bin/perl -w
use Socket;
use strict;
my ($rendezvous, $riga);
$rendezvous = shift || 'catsock';
socket(SOCK, PF_UNIX, SOCK_STREAM, 0) || die "socket: $!";
connect(SOCK, sockaddr_un($rendezvous)) || die "connect: $!";
while (defined($riga = <SOCK>)) {
print $riga;
}
exit;
Ed ecco un server corrispondente. Qui non dovete preoccuparvi di quegli stupidi terminatori, perché i socket del dominio Unix risiedono nell'host locale (garantito!), per cui funziona tutto nel modo corretto.
#!/usr/bin/perl -Tw
use strict;
use Socket;
use Carp;
BEGIN { $ENV{PATH} = '/usr/ucb:/bin' }
sub spawn; # dichiarazione anticipata
sub logmsg { print "$0 $$: @_ at ", scalar localtime, "\n" }
my $NOME = 'catsock';
my $uaddr = sockaddr_un($NOME);
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 "servizio partito su $NOME";
my $waitedpid;
use POSIX ":sys_wait_h";
sub MIETITORE {
my $child;
while (($waitedpid = waitpid(-1,WNOHANG)) > 0) {
logmsg "reaped $waitedpid" . ($? ? " with exit $?" : '');
}
$SIG{CHLD} = \&MIETITORE; # odiate sysV
}
$SIG{CHLD} = \&MIETITORE;
for ( $waitedpid = 0;
accept(Client,Server) || $waitedpid;
$waitedpid = 0, close Client)
{
next if $waitedpid;
logmsg "connessione su $NOME";
spawn sub {
print "Ehila`, sono le ", scalar localtime, "\n";
exec '/usr/games/fortune' or die "errore su exec fortune: $!";
};
}
sub spawn {
my $coderef = shift;
unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') {
confess "utilizzo: spawn CODEREF";
}
my $pid;
if (!defined($pid = fork)) {
logmsg "errore fork: $!";
return;
} elsif ($pid) {
logmsg "generato $pid";
return; # Sono il padre
}
# altrimenti sono il figlio, esegui il codice dato
open(STDIN, "<&Client") || die "errore dup client su stdin";
open(STDOUT, ">&Client") || die "errore dup client su stdout";
## open(STDERR, ">&STDOUT") || die "errore dup stdout su stderr";
exit &$coderef();
}
Come potete vedere, è piuttosto simile al server del dominio Internet, così tanto, in effetti, che abbiamo omesso molte funzioni duplicate -- spawn()
, logmsg()
, ctime()
, e MIETITORE()
-- che ci si aspetta che siano come nell'altro server.
Insomma, perché dovreste voler utilizzare un socket nel dominio Unix invece che una più semplice FIFO? Perché una FIFO non vi dà una sessione. Non potete distinguere i dati provenienti da un processo da quelli di un altro. Con la programmazione con i socket ottente una sessione separata per ciascun client: questo è il motivo per cui accept()
richiede due argomenti.
Ad esempio, diciamo che abbiate un demone di un server di database, che gira da molto tempo, che voialtri volete rendere accessibile dal Web, ma solo se si passa attraverso un'interfaccia CGI. Bene, in questo caso avrete un piccolo, semplice programma CGI che fa tutti i controlli e le registrazioni che vi aggradano, e poi agisce come client nel dominio Unix e si connette al vostro server privato.
Client TCP con IO::Socket
Per coloro che preferiscono un'interfaccia più ad alto livello alla programmazione dei socket, il modulo IO::Socket fornisce un approccio orientato agli oggetti. IO::Socket è incluso come parte della distribuzione standard di Perl a partire dalla versione 5.004. Se state utilizzando una versione precedente di Perl, non dovete far altro che scaricare IO::Socket da CPAN, dove potrete anche trovare dei moduli che forniscono delle interfacce semplici per i seguenti sistemi: DNS, FTP, Ident (RFC 931), NIS e NISPlus, NNTP, Ping, POP3, SMTP, SNMP, SSLeay, Telnet e Time -- tanto per nominarne qualcuno.
Un Semplice Client
Ecco un client che crea una connessione TCP al servizio "daytime" sulla porta 13 dell'host chiamato "localhost", e stampa tutto ciò che arriva dal server.
#!/usr/bin/perl -w
use IO::Socket;
$remoto = IO::Socket::INET->new(
Proto => "tcp",
PeerAddr => "localhost",
PeerPort => "daytime(13)",
)
or die "impossibile collegarsi alla porta daytime in localhost";
while ( <$remoto> ) { print }
Quando lanciate questo programma, dovreste ottenere indietro qualcosa che assomiglia a questo:
Wed May 14 08:40:46 MDT 1997
Ecco cosa significano quei parametri forniti al costruttore new
:
Proto
-
Questo specifica il protocollo da utilizzare. In questo caso, l'handle del socket restituito sarà connesso ad un socket, TCP, perché vogliamo una connessione orientata ad un flusso, ossia una che si comporti come se fosse un buon vecchio file. Non tutti i socket sono di questo tipo. Ad esempio, il protocollo UDP può essere utilizzato per costruire un socket a datagramma, utilizzato per passare messaggi.
PeerAddr
-
Questo è il nome, o l'indirizzo Internet, dell'host remoto su cui viene eseguito il processo server. Avremmo potuto specificare un nome più lungo, come
"www.perl.com"
, o un indirizzo come"204.148.40.9"
. Per scopi dimostrativi, abbiamo utilizzato il nome di host speciale"localhost"
, che dovrebbe sempre corrispondere alla macchina su cui state operando al momento. Il corrispondente indirizzo Internet per localhost è"127.1"
, se preferite utilizzarlo. PeerPort
-
Questo è il nome del servizio o il numero di porta a cui vorremmo connetterci. Avremmo potuto cavarcela semplicemente con
"daytime"
su sistemi con un file services correttamente configurato [NOTA: il file services di sistema si trova in /etc/services in Unix] ma, tanto per stare sicuri, abbiamo specificato il numero di porta (13) fra parentesi. Anche utilizzare solo il numero avrebbe funzionato, ma i numeri costanti rendono nervosi i programmatori accorti.
Avete notato che il valore restituito dal costruttore new
viene utilizzato come fosse un filehandle nel ciclo while
? Esso è quel che si chiama un filehandle indiretto, una variabile scalare che contiene un filehandle. Potete utilizzarlo nella stessa maniera che fareste con un filehandle normale. Ad esempio, potete leggervi una riga così:
$riga = <$handle>;
tutte le righe rimanenti in questo modo:
@righe = <$handle>;
ed inviarvi una riga di dati in quest'altro modo:
print $handle "un po' di dati\n";
Un Client Webget
Ecco un semplice client che accetta il nome di un host remoto da cui prendere un documento, e successivamente una lista di documenti da prendere da questo host. Questo client è più interessante del precedente, perché invia qualcosa al server prima di leggere la risposta del server.
#!/usr/bin/perl -w
use IO::Socket;
unless (@ARGV > 1) { die "utilizzo: $0 host documento ..." }
$host = shift(@ARGV);
$EOL = "\015\012";
$BLANK = $EOL x 2;
foreach $documento ( @ARGV ) {
$remoto = IO::Socket::INET->new( Proto => "tcp",
PeerAddr => $host,
PeerPort => "http(80)",
);
unless ($remoto) { die "errore connessione http su $host" }
$remote->autoflush(1);
print $remoto "GET $documento HTTP/1.0" . $BLANK;
while ( <$remoto> ) { print }
close $remoto;
}
Il server web fornisce il servizio "http", che si assume risiedere alla sua porta standard, numero 80. Se il server web che state cercando di contattare si trova ad una porta differente (come 1080 o 8080), dovreste specificare la coppia relativa, PeerPort => 8080
. Il metodo autoflush
viene chiamato sul socket perché altrimenti il sistema utilizzerebbe un buffer per l'output che vi mandiamo. (Se siete su un Mac, avrete anche bisogno di cambiare ciascun "\n"
nel vostro codice che invia dati sulla rete, modificandolo in "\015\012"
).
Connettersi ad un server è solo la prima parte di un processo: una volta che ottenete la connessione, dovete utilizzare il linguaggio del server. Ciascun server sulla rete ha il suo proprio piccolo linguaggio di comando, che attende in ingresso. La stringa che inviamo al server, che inizia con "GET", è in sintassi HTTP. In questo caso, stiamo semplicemente richiedendo ciascuno dei documenti specificati. Sì, stiamo effettivamente iniziando una nuova connessione per ciascun documento, anche se si tratta dello stesso host. Questo è il modo con cui avete sempre dovuto parlare HTTP. Versioni recenti dei browser web potrebbero chiedere al server remoto di lasciare la connessione aperta per un altro po', ma il server non è obbligato a soddisfare questa richiesta.
Ecco un esempio di esecuzione di quel programma, che chiameremo webget:
% 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
<HEAD><TITLE>404 File Not Found</TITLE></HEAD>
<BODY><H1>File Not Found</H1>
The requested URL /guanaco.html was not found on this server.<P>
</BODY>
Va bene, non è molto interessante, perché non ha trovato quel particolare documento. Ma una risposta lunga non sarebbe entrata in questa pagina.
Per una versione più ricca di caratteristiche di questo programma, dovreste dare un'occhiata al programma lwp-request incluso con i moduli LWP da CPAN.
Client Interattivo con IO::Socket
Orbene, fino ad ora è tutto a posto se volete inviare un comando e ricevere una singola risposta, ma che ne dite di costruire qualcosa di completamente interattivo, un po' come funziona telnet? In questo modo potete digitare una riga, ricevere la risposta, digitare un'altra riga, riceverne la risposta, ecc.
Questo client risulta più complicato dei due che abbiamo fatto fino ad ora, ma se siete in un sistema che supporta la potente chiamata di sistema fork
, la soluzione non è poi così difficile. Una volta che abbiate effettuato la connessione ad un qualsiasi servizio con il quale vogliate dialogare, chiamate fork
per clonare il vostro processo. Ciascuno di questi due processi identici deve fare una cosa molto semplice: il padre copia qualsiasi cosa venga dal socket sullo standard output, mentre il figlio contemporaneamente copia qualunque cosa arrivi sullo standard input verso il socket. Ottenere lo stesso risultato utilizzando un unico processo sarebbe molto più complicato, perché è più semplice programmare due processi per fare una cosa piuttosto che programmare un processo solo per farne due. (Questo principio del farlo-semplice è una pietra angolare della filosofia Unix, e pure di una buona ingegneria del software, il che probabilmente spiega perché si è diffusa agli altri sistemi).
Ecco il codice:
#!/usr/bin/perl -w
use strict;
use IO::Socket;
my ($host, $porta, $kidpid, $handle, $linea);
unless (@ARGV == 2) { die "utilizzo: $0 host porta" }
($host, $porta) = @ARGV;
# crea una connessione TCP all'host dato sulla porta indicata
$handle = IO::Socket::INET->new(Proto => "tcp",
PeerAddr => $host,
PeerPort => $porta)
or die "impossibile connettersi alla porta $porta su $host: $!";
$handle->autoflush(1); # cosi` l'output viene mandato subito
print STDERR "[Connesso a $host:$port]\n";
# dividi il programma in due gemelli identici
die "errore fork: $!" unless defined($kidpid = fork());
# il blocco if{} gira solo nel padre
if ($kidpid) {
# copia dal socket sullo standard output
while (defined ($linea = <$handle>)) {
print STDOUT $linea;
}
kill("TERM", $kidpid); # manda SIGTERM al figlio
}
# il blocco else{} viene eseguito solo dal figlio
else {
# copia lo standard input sul socket
while (defined ($linea = <STDIN>)) {
print $handle $linea;
}
}
La funzione kill
nel blocco if
del padre è lì per inviare un segnale al nostro processo figlio (che sta girando nel blocco else
) non appena il server remoto ha chiuso il suo lato della connessione.
Se il server remoto invia dati un ottetto alla volta, ed avete bisogno di prenderli subito senza aspettare che arrivi un carattere a-capo (che potrebbe anche non arrivare), potreste rimpiazzare il ciclo while
nel padre con il seguente:
my $ottetto;
while (sysread($handle, $ottetto, 1) == 1) {
print STDOUT $ottetto;
}
Effettuare una chiamata di sistema per ciascun ottetto da leggere non è molto efficiente (per fare un eufemismo), ma è il più semplice da spiegare e funziona ragionevolmente bene.
Server TCP con IO::Socket
Come sempre, metter su un server è un po' più complicato che lanciare un client. Il modello è che il server crea un particolare tipo di socket, che non fa altro che ascoltare su una porta particolare aspettando connessioni in ingresso. Può farlo grazie al metodo IO::Socket::INET->new()
, con argomenti leggermente differenti rispetto al client.
- Proto
-
Indica il protocollo da utilizzare. Come per i nostri client, qui continueremo ad usare
"tcp"
. - LocalPort
-
Specifichiamo una porta locale nell'argomento
LocalPort
, cosa che non abbiamo fatto nel client. Questo parametro rappresenta il nome del servizio o il numero di porta sul quale volete si agganci il server. (In Unix, le porte al di sotto della 1024 sono riservate al superutente). Nel nostro esempio, utilizzeremo la porta 9000, ma potete utilizzare qualsiasi porta libera nel vostro sistema. Se provate ad utilizzarne una occupata, riceverete un messaggio di errore "Address already in use" ["Indirizzo già utilizzato", NdT]. Sotto Unix, il comandonetstat -a
vi mostrerà quali servizi sono al momento forniti da un programma server. - Listen
-
Il parametro
Listen
imposta il massimo numero di connessioni pendenti che siamo disposti ad accettare prima di cominciare a respingere i client. Pensatelo come se fosse una coda di attesa per le chiamate al vostro telefono. Il modulo di basso livelloSocket
ha un simbolo speciale per il massimo valore di sistema, ossiaSOMAXCONN
. - Reuse
-
Il parametro
Reuse
è necessario per far sì che possiamo riavviare il nostro server a mano senza dover attendere qualche minuto per dare tempo ai buffer di sistema di liberarsi.
Una volta che il socket generico del server è stato creato utilizzando i parametri indicati, il server si mette in attesa che un nuovo client si colleghi. Il server si blocca nel metodo accept
, che alla fine accetta una connessione bidirezionale dal client remoto. (Assicuratevi di chiamare autoflush
su questo handle per aggirare il buffering).
Per aggiungere semplicità per l'utente, il nostro server chiede i comandi all'utente. La maggior parte dei server non lo fanno. Poiché inviamo la richiesta senza un carattere a-capo, dovrete utilizzare la variante sysread
del client interattivo descritto in precedenza.
Questo server accetta uno di cinque comandi differenti, inviando l'output al client. Osservate che, differentemente dalla maggior parte dei server di rete, questo gestisce solo un client per volta. I server con più thread sono trattati nel capitolo 6 del Camel [ossia il "Camel Book" o "Libro del Cammello", anche noto come "Programming Perl", NdT].
Ecco il codice.
#!/usr/bin/perl -w
use IO::Socket;
use Net::hostent; # per la versione OO di gethostbyaddr
$PORTA = 9000; # prendete una porta non utilizzata
$server = IO::Socket::INET->new( Proto => 'tcp',
LocalPort => $PORTA,
Listen => SOMAXCONN,
Reuse => 1);
die "impossibile lanciare il server" unless $server;
print "[Server $0 in attesa di connession dai client]\n";
while ($client = $server->accept()) {
$client->autoflush(1);
print $client "Benvenuto in $0; digitare aiuto per la lista dei comandi.\n";
$hostinfo = gethostbyaddr($client->peeraddr);
printf "[Connessione da %s]\n", $hostinfo ? $hostinfo->name : $client->peerhost;
print $client "Comando? ";
while ( <$client>) {
next unless /\S/; # riga vuota
if (/via|esci/i) { last; }
elsif (/data|ora/i) { printf $client "%s\n", scalar localtime; }
elsif (/chi/i ) { print $client `who 2>&1`; }
elsif (/biscottino/i ) { print $client `/usr/games/fortune 2>&1`; }
elsif (/motd/i ) { print $client `cat /etc/motd 2>&1`; }
else {
print $client "Comandi: esci data chi biscottino motd\n";
}
} continue {
print $client "Comando? ";
}
close $client;
}
UDP: Scambio di Messaggi
Un altro tipo di impostazione client-server è quella che non utilizza connessioni, ma messggi. Le comunicazioni UDP richiedono molto meno sforzo, ma di contro forniscono anche una minore affidabilità, poiché non ci sono garanzie che i messaggi arriveranno, men che meno che lo facciano in ordine ed integri. Nonostante ciò, UDP offre alcuni vantaggi rispetto al TCP, inclusa la possibilità di inviare pacchetti in broadcast [ossia, a tutti gli host in una rete, NdT] o in multicast [ossia, ad una molteplicità di host, NdT] verso un bel mucchio di host di destinazione contemporaneamente (di solito nella vostra sottorete locale). Se avete problemi di affidabilità e cominciate ad inserire controlli nel vostro sistema di messaggi, allora dovreste probabilmente utilizzare TCP.
Osservate che i datagrammi UDP non costituiscono un flusso di ottetti e non dovrebbero essere trattati come tali. Ciò rende l'utilizzo dei meccanismi di I/O con bufferizzazione interna come stdio (ad esempio print()
e compagnia bella) particolarmente bizzarro. Utilizzate syswrite()
o, meglio, send()
, come nell'esempio a seguire.
Ecco un programma UDP simile al client TCP Internet di esempio dato in precedenza. In ogni caso, invece di controllare un host per volta, la versione UDP li controllerà asincronamente, simulando un multicast ed utilizzando select()
per effettuare un'attesa di I/O con timeout. Per fare qualcosa di simile con il TCP, dovreste utilizzare un handle di socket differente per ciascun host.
#!/usr/bin/perl -w
use strict;
use Socket;
use Sys::Hostname;
my ( $contatore, $hisiaddr, $hispaddr, $histime,
$host, $iaddr, $paddr, $porta, $proto,
$rin, $rout, $rtime, $SECONDI_IN_70_ANNI);
$SECONDI_IN_70_ANNI = 2208988800;
$iaddr = gethostbyname(hostname());
$proto = getprotobyname('udp');
$porta = 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\n", "localhost", 0, scalar localtime time;
$contatore = 0;
for $host (@ARGV) {
$contatore++;
$hisiaddr = inet_aton($host) || die "host sconosciuto";
$hispaddr = sockaddr_in($porta, $hisiaddr);
defined(send(SOCKET, 0, 0, $hispaddr)) || die "send() $host: $!";
}
$rin = '';
vec($rin, fileno(SOCKET), 1) = 1;
# timeout dopo 10.0 secondi
while ($contatore && select($rout = $rin, undef, undef, 10.0)) {
$rtime = '';
($hispaddr = recv(SOCKET, $rtime, 4, 0)) || die "recv: $!";
($porta, $hisiaddr) = sockaddr_in($hispaddr);
$host = gethostbyaddr($hisiaddr, AF_INET);
$histime = unpack("N", $rtime) - $SECONDI_IN_70_ANNI;
printf "%-12s ", $host;
printf "%8d %s\n", $histime - time, scalar localtime($histime);
$contatore--;
}
Osservate che questo esempio non include alcuna ripetizione dei tentativi andati male, e potrebbe di conseguenza fallire nel contattare un host raggiungibile. La ragione più importante alla base di un possibile fallimento è la congestione nelle code nell'host di invio se il numero di host nella lista è sufficientemente grande.
IPC SysV
Mentre l'IPC System V non è così ampiamente utilizzata come i socket, mantiene tuttavia alcuni utilizzi interessanti. In ogni caso, non potete utilizzare l'IPC SysV o la mmap()
Berkeley per avere memoria condivisa in modo da condividere una variabile fra più processi. Questo accade perché Perl riallocherebbe le vostre stringhe quando meno ve lo aspettate.
Ecco un piccolo esempio che mostra l'utilizzo della memoria condivisa.
use IPC::SysV qw(IPC_PRIVATE IPC_RMID S_IRWXU);
$grandezza = 2000;
$id = shmget(IPC_PRIVATE, $grandezza, S_IRWXU) || die "$!";
print "chiave shm $id\n";
$messaggio = "Messaggio #1";
shmwrite($id, $messaggio, 0, 60) || die "$!";
print "scritto: '$messaggio'\n";
shmread($id, $buff, 0, 60) || die "$!";
print "letto : '$buff'\n";
# il buffer di shmread e` riempito alla fine con ottetti nulli
substr($buff, index($buff, "\0")) = '';
print "in" unless $buff eq $messaggio;
print "giusto\n";
print "cancello shm $id\n";
shmctl($id, IPC_RMID, 0) || die "$!";
Ecco un esempio di un semaforo:
use IPC::SysV qw(IPC_CREAT);
$IPC_KEY = 1234;
$id = semget($IPC_KEY, 10, 0666 | IPC_CREAT ) || die "$!";
print "chiave shm $id\n";
Mettete questo codice in un file separato in modo da lanciarlo in più di un processo. Chiamate il file prendi:
# crea un semaforo
$IPC_KEY = 1234;
$id = semget($IPC_KEY, 0 , 0 );
die if !defined($id);
$semnum = 0;
$semflag = 0;
# 'prendi' il semaforo
# attendi che il semaforo vada a zero
$semop = 0;
$opstring1 = pack("s!s!s!", $semnum, $semop, $semflag);
# Incrementa il contatore del semaforo
$semop = 1;
$opstring2 = pack("s!s!s!", $semnum, $semop, $semflag);
$opstring = $opstring1 . $opstring2;
semop($id,$opstring) || die "$!";
Mettete questo codice in un file separato in modo da lanciarlo in più di un processo. Chiamate il file dai:
# 'dai' il semaforo
# lanciate questo nel processo originale e vedrete
# che il secondo processo continua
$IPC_KEY = 1234;
$id = semget($IPC_KEY, 0, 0);
die if !defined($id);
$semnum = 0;
$semflag = 0;
# Decrementa il contatore del semaforo
$semop = -1;
$opstring = pack("s!s!s!", $semnum, $semop, $semflag);
semop($id,$opstring) || die "$!";
Il codice di IPC SysV qui sopra è stato scritto tanto tempo fa, ed ha un'aria indiscutibilmente goffa. Per dargli un aspetto più moderno, consultate il modulo IPC::SysV
che è incluso in Perl a partire dalla versione 5.005.
Un piccolo esempio che dimostra le code di messaggi SysV:
use IPC::SysV qw(IPC_PRIVATE IPC_RMID IPC_CREAT S_IRWXU);
my $id = msgget(IPC_PRIVATE, IPC_CREAT | S_IRWXU);
my $inviato = "message";
my $tipo_inviato = 1234;
my $ricevuto;
my $tipo_ricevuto;
if (defined $id) {
if (msgsnd($id, pack("l! a*", $tipo_inviato, $inviato), 0)) {
if (msgrcv($id, $ricevuto, 60, 0, 0)) {
($tipo_ricevuto, $ricevuto) = unpack("l! a*", $ricevuto);
if ($ricevuto eq $inviato) {
print "tutto a posto\n";
} else {
print "qualcosa e` andato male\n";
}
} else {
die "# msgrcv e` fallita\n";
}
} else {
die "# msgsnd e` fallita\n";
}
msgctl($id, IPC_RMID, 0) || die "# msgctl e` fallita: $!\n";
} else {
die "# msgget e` fallita\n";
}
NOTE
La maggior parte delle funzioni presentate restituisce, silenziosamente ma educatamente, undef
quando falliscono, invece di causare la terminazione del vostro programma per via di un'eccezione non raccolta. (A dire il vero, alcune delle nuove funzioni di conversione in Socket lanciano croak()
quando gli argomenti sono scorretti). È pertanto essenziale che controlliate i valori restituiti da queste funzioni. Iniziate sempre i vostro programmi con i socket nel modo che segue per avere una riuscita ottimale, e non dimenticate di aggiungere l'opzione di controllo taint -T alla riga #!
per i server:
#!/usr/bin/perl -Tw
use strict;
use sigtrap;
use Socket;
BUG
Tutte queste funzioni creano problemi di portabilità specifici per i vari sistemi. Come già osservato, Perl è alla mercé delle vostre librerie C per molta parte del suo comportamento di sistema. È probabilmente più sicuro assumere che i segnali abbiano la semantica bacata di SysV, ed utilizzare operazioni semplici con i socket TCP e UDP; ad esempio, non tentate di passare descrittori di file aperti attraverso un datagramma UDP locale se volete mantenere una possibilità che il vostro codice sia portabile.
AUTORE
Tom Christiansen, con occasionali tracce della versione originale di Larry Wall e suggerimenti dai Perl Porters [il gruppo che si occupa di rendere Perl portabile su varie piattaforme, NdT].
SI VEDA ANCHE
C'è molto più da interconnettersi di quanto avete visto, ma dovrebbe esservi sufficiente per partire.
Per i programmatori intrepidi, il libro di testo indispensabile è Unix Network Programming, 2nd Edition, Volume 1 ["Programmazione delle Reti in Unix, seconda edizione, volume 1", NdT] di W. Richard Stevens (pubblicato da Prentice-Hall). Osservate che la maggior parte dei libri sulle reti affrontano il problema dal punto di vista di un programmatore C; la traduzione in Perl è lasciata come esercizio per il lettore.
La pagina del manuale IO::Socket(3) descrive la libreria di oggetti, mentre quella Socket(3) descrive l'interfaccia di basso livello ai socket. Oltre alle funzioni ovvie in perlfunc, dovreste anche controllare il file moduli nel sito CPAN più vicino. (Consultate perlmodlib o, meglio ancora, la FAQ Perl per una descrizione di cosa sia CPAN e di dove trovarlo).
La sezione 5 del file moduli è dedicata a "Networking, Device Control (modems), and Interprocess Communication" ["Reti, Dispositivi di Controllo (modem) e Comunicazione Interprocesso", NdT]; essa contiene numerosi moduli per il networking, operazioni Chat ed Expect, programmazione CGI, DCE, FTP, IPC, NNTP, Proxy, Ptty, RPC, SNMP, SMTP, Telnet, Thread e ToolTalk -- tanto per nominarne qualcuno.
TRADUZIONE
Versione
La versione su cui si basa questa traduzione è ottenibile con:
perl -MPOD2::IT -e print_pod perlipc
Per maggiori informazioni sul progetto di traduzione in italiano si veda http://pod2it.sourceforge.net/.
Traduttore
Traduzione a cura di Flavio Poletti.
Revisore
Revisione a cura di dree.