perlipc - Comunicazione Interprocesso in Perl (segnali, fifo, pipe,
sottoprocessi robusti, socket e semafori)
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.
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.
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__
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
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 utilizzando stdio della libreria di sistema, la
libreria stessa potrebbe ri-lanciare la read 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 POSIX sigaction() , 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 utilizzano SA_RESTART . Di conseguenza, le chiamate
di sistema riavviabili possono fallire (con $! impostato a
EINTR ) laddove prima avrebbero avuto successo.
Osservate che il layer di default :perlio riproverà a lanciare
read , write e close come descritto in precedenza, e che
le chiamate wait e waitpid interrotte verranno sempre
ritentate.
- Segnali come ``indicatori di fallimento''
-
Alcuni segnali, come ad esempio
SEGV , ILL e BUS , 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 come CLD in certi sistemi), che indica che un processo figlio
è terminato. Su alcuni sistemi operativi si suppone che l'handler
chiami wait per il processo figlio completato. Su tali sistemi lo
schema dei segnali differiti non funzionerà per questi segnali (non
effettua la wait ). 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 chiamata wait .
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).
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: $!";
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.
[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).
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.
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.
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: the IO::Pty manpage e
the IO::Stty manpage. 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.
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;
}
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).
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.
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);
}
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.
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.
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";
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.
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.
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 comando netstat -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 livello Socket ha un simbolo speciale
per il massimo valore di sistema, ossia SOMAXCONN .
- 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;
}
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.
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";
}
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;
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.
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].
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 the IO::Socket(3) manpage 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.
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/.
Traduzione a cura di Flavio Poletti.
Revisione a cura di dree.
Mon Jun 11 22:02:16 2012
|