=head1 NOME perlipc - Comunicazione Interprocesso in Perl (segnali, fifo, pipe, sottoprocessi robusti, socket e semafori) =head1 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. =head1 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 I [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 puE essere generato intenzionalmente da una particolare sequenza sulla tastiera, come ad esempio Control-C o Control-Z; puE essere mandato da un altro processo; infine, puE 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 I 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 I; osservate come, nell'esempio, tutto quello che facciamo E 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. CiE significava che fare praticamente I nel vostri I poteva in teoria generare una mancanza di memoria e conseguentemente un core dump - leggete L piE avanti. I nomi dei segnali sono quelli elencati dal comando C nel vostro sistema; in alternativa, potete prenderli dal modulo Config. Impostate una lista C<@signame> indicizzata per numero per prendere il nome, ed una tabella C<%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 C<'IGNORE'> o C<'DEFAULT'> come I; in questo caso, Perl cercherE di scartare il segnale o di eseguire l'azione di default. Sulla maggior parte delle piattaforme Unix, il segnale C (a volte noto come C) ha un comportamento speciale rispetto all'impostazione di C<'IGNORE'>. Impostando C<$SIG{CHLD}> a C<'IGNORE'> su tali piattaforme, infatti, ha l'effetto di non creare processi zombie quando il processo padre si dimentica di eseguire C sui propri processi figlio (ossia, i processi figli sono eliminati automaticamente). Chiamare C con C<$SIG{CHLD}> impostato a C<'IGNORE'> di solito restituisce C<-1> su tali piattaforme. Alcuni segnali non possono essere nE intercettati nE ignorati, come KILL e STOP (ma non TSTP). Una strategia per ignorare temporaneamente i segnali consiste nell'utilizzo dell'istruzione C, che viene automaticamente ripristinata una volta che il vostro blocco termina. (Ricordatevi che i valori C-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 I [I, NdT] a tutti i processi nel gruppo corrente (ed imposta C<$SIG{HUP}> ad C<'IGNORE'> per evitare di uccidere sE stesso): { local $SIG{HUP} = 'IGNORE'; kill HUP => -$$; # un modo esotico per scrivere: kill('HUP', -$$) } Un altro segnale interessante da mandare E il numero zero. Questo segnale non ha un vero effetto su un processo figlio, ma controlla se questi E 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 E identico a quello del processo che lo manda, il segnale numero zero puE fallire perchE vi mancano i permessi per mandare il segnale, anche se il processo E vivo. Potreste essere in grado di determinare la causa del fallimento utilizzando C<%!>: unless (kill 0 => $pid or $!{EPERM}) { warn "$pid sembra deceduto"; } Potreste anche volere impiegare funzioni anonime per gli I piE semplici: $SIG{INT} = sub { die "\nFuori di qui!\n" }; Questo, perE, risulterebbe problematico per I piE complicati che hanno bisogno di re-installarsi. PoichE il meccanismo dei segnali di Perl E al momento basato sulla funzione C della libreria C, potete talvolta essere cosE sfortunati da incorrere in sistemi dove tale funzione E "rotta", ossia in cui si comporta nel vecchio ed inaffidabile modo SysV invece che nel nuovo, piE ragionevole approccio BSD e POSIX. Per questo motivo, vedrete che chi si mantiene sulla difensiva scrive I 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 E utilizzata, in Unix, anche per i timeout. Mentre siete al sicuro all'interno di un blocco C, impostate un I 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 E terminata ma prima di uscire dal blocco C. Se va oltre il tempo stabilito, utilizzerete C per saltare fuori dal blocco, in maniera analoga a come potreste utilizzare C o C 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 E C o C, questa tecnica E soggetta alla generazione di zombie. Se la cosa vi preoccupa, avrete bisogno di mettere in piedi una vostra coppia di C e C, ed uccidere i processi figli erranti. Per una gestione dei segnali piE complessa, potreste voler dare un'occhiata al modulo standard POSIX. Con grosso rammarico, il modulo E quasi interamente non documentato, ma il file F dalla distribuzione sorgente di Perl contiene qualche esempio. =head2 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 E detto I [I in inglese, da I, ossia controllore di disco e di esecuzione. La traduzione italiana non consente ovviamente di mantenere l'acronimo, e si utilizza la traduzione letterale I, NdT]. Se un processo demone ha un file di configurazione che viene modificato dopo che il processo E partito, dovrebbe esserci un modo per dirgli di rileggere tale file senza che, perE, termini l'esecuzione del processo stesso. Molti demoni mettono a disposizione questo meccanismo mediante un I opportuno del segnale C. Quando volete notificare al demone di rileggere il file, basta semplicemente che gli inviate il segnale C. Non tutte le piattaforme reinstallano automaticamente i loro I di segnale (nativi) dopo che un segnale E stato consegnato. Questo significa che l'I lavora solo la prima volta che il segnale viene inviato. La soluzione a questo problema consiste nell'utilizzare I C laddove disponibili, poichE il loro comportamento E ben definito. Il seguente esempio implementa un semplice demone, che fa in modo di ripartire ogni volta che viene ricevuto il segnale C. Il codice vero e proprio E posto nella funzione C, 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__ =head1 Pipe con nome Una pipe con nome (spesso detta FIFO [questo sarE il termine che useremo da qui in poi, NdT]) E 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 C. use POSIX qw( mkfifo ); mkfifo($percorso, 0700) or die "errore in mkfifo $percorso: $!"; Potete anche utilizzare il comando Unix C o, su alcuni sistemi, C. 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 E 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 bloccherE finchE non c'E qualcos'altro dall'altra parte. Ad esempio, supponiamo che vogliate spedire il vostro file F<.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 C, ecc.) prova a leggere da quel file, tale programma in lettura si bloccherE ed il vostro programma fornirE 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 =head2 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 C o C), ed il vostro C<$handler> poi chiama di nuovo la stessa funzione, potreste ottenere un comportamento non predicibile - spesso un I [salvataggio del nucleo centrale di un processo all'interno di un file]. Secondo, Perl stesso non E rientrante al suo livello piE 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 ciE, 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'I. Impostare un valore intero giE esistente che ha giE un valore e rientrare. Anche se quanto detto E un po' poco per il vero paranoico, che evita di utilizzare C all'interno di un C perchE il sistema I lE 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'I, 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 I e l'I 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'I a livello Perl da C<%SIG>. Lo schema "differito" consente molta piE flessibilitE nello scrivere gli I di segnale perchE 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: =over 4 =item Opcode che richiedono molto tempo PoichE 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 verrE visto finchE l'operazione non termina. =item IO Interrotto Quando viene consegnato un segnale (ad esempio INT, con Control-C), il sistema operativo interrompe le operazioni di IO come C (utilizzata per implementare l'operatore Perl EE). Nelle versioni piE vecchie di Perl l'I era chiamato immediatamente (e poichE C non E "insicura" il tutto funzionava bene). Con lo schema "differito" l'I non viene chiamato subito, e se Perl sta utilizzando C della libreria di sistema, la libreria stessa potrebbe ri-lanciare la C senza restituire il controllo a Perl e dargli una possibilitE di chiamare l'I in C<%SIG>. Se nel vostro sistema accade questo, la soluzione consiste nell'utilizzare lo strato C<:perlio> per fare IO - almeno su quegli I che volete che siano in grado di interrompersi con i segnali. (Lo strato C<:perlio> controlla i flag dei segnali e chiama gli I in C<%SIG> prima di ripristinare le operazioni di IO). Da notare che, in Perl 5.7.3 e successivi, il comportamento di default E quello di utilizzare lo strato C<:perlio> automaticamente. Osservate anche che alcune funzioni di libreria sul I, come C, 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 C, che ignora i segnali "sicuri" di Perl (notare che ciE significa sottoporsi volontariamente a possibili corruzioni in memoria, come descritto in precedenza). Invece di impostare C<$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"; =item Chiamate di sistema riavviabili Sui sistemi che lo supportano, le vecchie versioni di Perl utilizzavano il I C nella fase di installazione degli I C<%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 I utilizzano C. Di conseguenza, le chiamate di sistema riavviabili possono fallire (con C<$!> impostato a C) laddove prima avrebbero avuto successo. Osservate che il layer di default C<:perlio> riproverE a lanciare C, C e C come descritto in precedenza, e che le chiamate C e C interrotte verranno sempre ritentate. =item Segnali come "indicatori di fallimento" Alcuni segnali, come ad esempio C, C e C, vengono generati in conseguenza a fallimenti vari, come nella memoria virtuale. Di solito questi errori sono fatali, e c'E poco che un I 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 I C<%SIG> per questi segnali il nuovo schema non fa altro che impostare un I ed uscire, come descritto. CiE puE forzare il sistema operativo a ritentare l'istruzione che ha generato l'errore e - visto che non E cambiato niente - il segnale verrE generato di nuovo. Il risultato E un "ciclo" piuttosto bizzarro. In futuro, il meccanismo di gestione dei segnali in Perl potrebbe essere cambiato per evitare tutto ciE - possibilmente disabilitando semplicemente gli I C<%SIG> su segnali di quel tipo. Fino ad allora, la soluzione consiste nell'evitare di impostare un I in C<%SIG> per quei segnali. (Quali siano esattamente questi segnali dipende dal particolare sistema operativo). =item Segnali generati dallo stato del sistema operativo Su alcuni sistemi operativi, certi I di segnale dovrebbero "fare qualcosa" prima di uscire. Un esempio puE essere C (anche noto come C in certi sistemi), che indica che un processo figlio E terminato. Su alcuni sistemi operativi si suppone che l'I chiami C per il processo figlio completato. Su tali sistemi lo schema dei segnali differiti non funzionerE per questi segnali (non effettua la C). Di nuovo, il fallimento apparirE come un ciclo perchE il sistema operativo rigenererE il segnale, dal momento che ci sono processi figlio su cui non E stata chiamata C. =back Se volete tornare al vecchio comportamento per la gestione dei segnali, senza curarvi delle possibili corruzioni di memoria, impostate la variabile di ambiente C su C<"unsafe"> (una nuova caratteristica introdotta da Perl 5.8.1). =head1 Utilizzare C per IPC L'istruzione Perl base C puE essere utilizzata per effettuare comunicazioni interprocesso aggiungendo, o premettendo, un simbolo I [la sbarretta verticale "|", NdT] al secondo parametro di C. 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 () { next if /^(tcp|udp)/; print; } close STATUS || die "errore in chiusura: $! $?"; Se siete sicuri che un particolare programma E uno script Perl che si aspetta di ricevere nomi di file in C<@ARGV>, il programmatore furbo puE scrivere qualcosa del genere: % programma f1 "comando1|" - f2 "comando2|" f3 < tmpfile e, indipendentemente da quale tipo di I viene chiamato, il programma Perl leggerE dal file F, dal processo F, da I (ossia, F in questo caso), dal file F, dal comando F ed infine dal file F. Carino eh? Potreste osservare che E possibile utilizzare i I [le virgolette singole rovesciate "`", NdT] per ottenere un effetto pressochE uguale all'avere una pipe in lettura: print grep { !/^(tcp|udp)/ } `netstat -an 2>&1`; die "netstat fallito" if $?; Se ciE E vero in superficie, E molto piE efficiente lavorare una riga alla volta, perchE non avete bisogno di caricare l'intero risultato in memoria tutto insieme. Vi dE anche un controllo piE 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 C che da C. Se state scrivendo su una pipe, dovreste anche intercettare C. Pensate a cosa succederebbe, altrimenti, quando lanciate una pipe verso un comando che non esiste: la C avrE molto probabilmente successo (poichE riflette il successo della C), ma poi la vostra uscita fallirE -- in maniera spettacolare. Perl non puE sapere se il comando ha funzionato, perchE in realtE questo viene eseguito in un processo separato in cui C 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 vedrE recapitare un segnale che E 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 esploderE fino alla C, e scoppierE con un C. 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: $!"; =head2 Filehandle Sia il proceso principale che qualunque processo figlio esso generi, condividono gli stessi I C, C e C. Se entrambi i processi tentano di accedervi allo stesso momento, potrebbero succedere strane cose. Potreste inoltre voler chiudere e riaprire i I per il processo figlio. Potete aggirare questo problema aprendo la pipe con C, ma su alcuni sistemi questo significa che il processo figlio non puE sopravvivere al processo padre. =head2 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 I con: system("cmd &"); I I C e C (insieme, forse, a C, dipendentemente dalla vostra shell) saranno gli stessi del processo padre. Non avrete bisogno di intercettare C a causa del fatto che ha luogo una doppia C (guardate piE avanti per maggiori dettagli). =head2 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 E spesso chiamato "demonizzazione". Un demone che si comporti a modo si sposterE anche nella directory radice con C (in modo da non bloccare un eventuale C del filesystem contenente la directory da dove E stato lanciato) e redirigerE i propri descrittori di file standard da e su F (in modo che un output casuale non andrE 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 C deve essere effettuata prima di C in modo da assicurare che non siate leader del gruppo di processi (in tal caso C fallirebbe). Se il vostro sistema non ha C, aprite F ed utilizzate la C C su di esso. Consultate L per i dettagli. Gli utenti non-Unix dovrebbero controllare il modulo ProprioSistemaOperativo::Process per altre soluzioni. =head2 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 C accetta un argomento come file che puE essere o C<"-|"> o C<"|-"> per fare una cosa molto interessante: genera un processo figlio connesso al I che state aprendo. Il figlio esegue lo stesso programma del padre. CiE risulta utile per aprire un file in maniera sicura sotto una determinata coppia UID o GID, ad esempio. Se aprite una pipe I "meno", potete scrivere sul I che state aprendo ed il processo figlio se lo troverE nel proprio C. Se aprite una pipe I "meno", invece, potete leggere dal I aperto qualsiasi cosa il processo figlio scriva sul proprio C. 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 () { 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 C sarebbe immediato, ma non potete utilizzare una C di una pipe o i I in maniera sicura. Questo si ha perchE non c'E modo di impedire alla shell di mettere le proprie mani sui vostri argomenti. Utilizzate invece il controllo di basso livello su C direttamente. Ecco un I 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 () { # 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 C per le pipe; la sintassi: open FIGLIO_PS, "-|", "ps", "aux" or die $!; effettua un fork del comando L (senza lanciare a sua volta una shell, visto che ci sono piE di tre argomenti per C), e legge il relativo output standard utilizzando il I C. E implementata anche la sintassi corrispondente per scrivere su pipe di comandi (con C<"|-"> al posto di C<"-|">). Osservate che queste operazioni sono delle C 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 piE sul threading, consultate i F citati di seguito nella sezione L. =head2 Comunicazione Bidirezionale con un Altro Processo Mentre tutto ciE funziona ragionevolmente bene per comunicazioni unidirezionali, come realizziamo una comunicazione bidirezionale? In realtE, 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 C o il parametro B<-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 C per cattuare entrambe le estremitE. Esiste anche una C per I/O tridirezionale, in modo da catturare anche lo C del processo figlio, ma in questo modo dovreste utilizzare un loop contorto con C e non vi consentirebbe di utilizzare le normali operazioni di input di Perl. Se date un'occhiata al sorgente, noterete che C utilizza primitive di basso livello come C Unix e chiamate ad C per creare tutte le connessioni. Mentre avrebbe potuto essere leggermente piE efficiente utilizzare C, sarebbe stato meno portabile di quanto non E in realtE. Le funzioni C e C con buona probabilitE non funzioneranno se non su un sistema Unix o su qualche altro che sia aderente a POSIX. Ecco un esempio di utilizzo di C: use FileHandle; use IPC::Open2; $pid = open2(*Lettore, *Scrittore, "cat -u -n" ); print Scrittore "stuff\n"; $preso = ; Il problema qui E che la bufferizzazione Unix vi rovinerE veramente la giornata. Anche se il vostro I C si libera automaticamente [I, NdT], ed il processo dall'altra parte raccoglierE 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, perchE abbiamo passato l'opzione B<-u> al programma C 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 F. Questa utilizza I per indurre il vostro programma a comportarsi in maniera piE 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 C contiene anche le funzioni C e C. Cercate la libreria (e speriamo il suo successore F) nell'archivio CPAN piE vicino, come spiegato nella sezione C piE avanti. Il modulo piE nuovo L su CPAN punta a risolvere questo tipo di problemi. Esso richiede altri due moduli da CPAN: L e L. Imposta uno I 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. =head2 Comunicazione Bidirezionale con Voi Stessi Se volete, potete effettuare una chiamata di basso livello a C e C per metterle insieme con le vostre mani. L'esempio che segue parla da sE, ma potreste riaprire i I appropriati verso C e C 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 = ); 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 = ); 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 realtE, non dovete effettuare esattamente due chiamate a C. Se avete la chiamata di sistema C, essa farE 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 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 = ); 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 = ); print "Pid Figlio $$ ha appena letto questo: `$line'\n"; print PADRE "Pid Figlio $$ sta mandando questo\n"; close PADRE; exit; } =head1 Socket: Comunicazione Client/Server Nonostante non siano limitati ai sistemi operativi derivati da Unix (ad esempio, WinSock sui PC dE supporto ai socket, cosE come alcune librerie su VMS), potreste non avere i socket sul vostro sistema, nel qual caso questa sezione non sarE 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 piE, 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 I Perl funzionano differentemente dai descrittori di file in C; secondo, Perl conosce giE 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 portabilitE. Se vi capita di vedere codice che fa cose come impostare esplicitamente C<$AF_INET = 2>, sappiate che siete in guai grossi: un approccio immensamente superiore consiste nell'utilizzare il modulo C, che dE un accesso molto piE 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 verrE 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). =head2 Terminatori di Riga in Internet Il terminatore di riga in Internet E "\015\012". In alcune varianti ASCII di Unix, questa sequenza puE 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" E conforme ("siate fiscali in quello che fornite..."), ma raccomandano anche di accettare uno "\012" da solo in ingresso ("... ma siate pazienti con ciE che ricevete."). Non siamo stati sempre bravissimi su tutto ciE nel codice di questo documento, ma non dovreste trovarvi male, a meno che non siate su un Mac. =head2 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 = )) { print $riga; } close (SOCK) || die "close: $!"; exit; Ed ecco un server conforme, di modo che funzioni. Lasceremo l'indirizzo pari a C, in modo che il kernel possa scegliere l'interfaccia piE appropriata su host con piE 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. E 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 C per ogni richiesta in arrivo. In questo modo puE gestire molte richieste allo stesso tempo, ma potrebbe non essere il comportamento che desiderate in ogni momento. Anche se non chiamate C, la funzione C vi consentirE di avere molte connessioni pendenti. Usare C per generare i processi serventi deve essere fatto con particolare cautela nella ripulitutra dei figli deceduti (anche detti "zombie" nel gergo Unix), perchE altrimenti riempirete rapidamente la vostra tabella dei processi. Vi consigliamo di utilizzare l'opzione B<-T> per attivare il I [controllo di marcatura per dati inattendibili, NdT], anche se il processo non sono in esecuzione C o C. E sempre una buona idea farlo per server ed altri programmi che sono eseguiti per conto di qualcun altro (come gli script CGI), perchE riduce le eventualitE 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); } =head2 Client e Server TCP nel Dominio Unix Tutto ciE 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 C. % 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 B<-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 = )) { print $riga; } exit; Ed ecco un server corrispondente. Qui non dovete preoccuparvi di quegli stupidi terminatori, perchE 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, E piuttosto simile al server del dominio Internet, cosE tanto, in effetti, che abbiamo omesso molte funzioni duplicate -- C, C, C, e C -- che ci si aspetta che siano come nell'altro server. Insomma, perchE dovreste voler utilizzare un socket nel dominio Unix invece che una piE semplice FIFO? PerchE una FIFO non vi dE 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 E il motivo per cui C 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. =head1 Client TCP con IO::Socket Per coloro che preferiscono un'interfaccia piE ad alto livello alla programmazione dei socket, il modulo IO::Socket fornisce un approccio orientato agli oggetti. IO::Socket E 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. =head2 Un Semplice Client Ecco un client che crea una connessione TCP al servizio "daytime" sulla porta 13 dell'host chiamato "localhost", e stampa tutto ciE 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 C: =over 4 =item C Questo specifica il protocollo da utilizzare. In questo caso, l'I del socket restituito sarE connesso ad un socket, TCP, perchE 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 puE essere utilizzato per costruire un socket a datagramma, utilizzato per passare messaggi. =item C Questo E il nome, o l'indirizzo Internet, dell'host remoto su cui viene eseguito il processo server. Avremmo potuto specificare un nome piE lungo, come C<"www.perl.com">, o un indirizzo come C<"204.148.40.9">. Per scopi dimostrativi, abbiamo utilizzato il nome di host speciale C<"localhost">, che dovrebbe sempre corrispondere alla macchina su cui state operando al momento. Il corrispondente indirizzo Internet per localhost E C<"127.1">, se preferite utilizzarlo. =item C Questo E il nome del servizio o il numero di porta a cui vorremmo connetterci. Avremmo potuto cavarcela semplicemente con C<"daytime"> su sistemi con un file I correttamente configurato [NOTA: il file I di sistema si trova in I 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. =back Avete notato che il valore restituito dal costruttore C viene utilizzato come fosse un I nel ciclo C? Esso E quel che si chiama un I indiretto, una variabile scalare che contiene un I. Potete utilizzarlo nella stessa maniera che fareste con un I normale. Ad esempio, potete leggervi una riga cosE: $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"; =head2 Un Client I 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 E piE interessante del precedente, perchE 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, C<< PeerPort => 8080 >>. Il metodo C viene chiamato sul socket perchE altrimenti il sistema utilizzerebbe un buffer per l'output che vi mandiamo. (Se siete su un Mac, avrete anche bisogno di cambiare ciascun C<"\n"> nel vostro codice che invia dati sulla rete, modificandolo in C<"\015\012">). Connettersi ad un server E 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", E in sintassi HTTP. In questo caso, stiamo semplicemente richiedendo ciascuno dei documenti specificati. SE, stiamo effettivamente iniziando una nuova connessione per ciascun documento, anche se si tratta dello stesso host. Questo E il modo con cui avete sempre dovuto parlare HTTP. Versioni recenti dei I web potrebbero chiedere al server remoto di lasciare la connessione aperta per un altro po', ma il server non E obbligato a soddisfare questa richiesta. Ecco un esempio di esecuzione di quel programma, che chiameremo I: % webget www.perl.com /guanaco.html HTTP/1.1 404 File Not Found Date: Thu, 08 May 1997 18:02:32 GMT Server: Apache/1.2b6 Connection: close Content-type: text/html 404 File Not Found

File Not Found

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

Va bene, non E molto interessante, perchE non ha trovato quel particolare documento. Ma una risposta lunga non sarebbe entrata in questa pagina. Per una versione piE ricca di caratteristiche di questo programma, dovreste dare un'occhiata al programma I incluso con i moduli LWP da CPAN. =head2 Client Interattivo con IO::Socket Orbene, fino ad ora E 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 I? In questo modo potete digitare una riga, ricevere la risposta, digitare un'altra riga, riceverne la risposta, ecc. Questo client risulta piE complicato dei due che abbiamo fatto fino ad ora, ma se siete in un sistema che supporta la potente chiamata di sistema C, la soluzione non E poi cosE difficile. Una volta che abbiate effettuato la connessione ad un qualsiasi servizio con il quale vogliate dialogare, chiamate C 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 I, mentre il figlio contemporaneamente copia qualunque cosa arrivi sullo I verso il socket. Ottenere lo stesso risultato utilizzando un unico processo sarebbe I piE complicato, perchE E piE semplice programmare due processi per fare una cosa piuttosto che programmare un processo solo per farne due. (Questo principio del farlo-semplice E una pietra angolare della filosofia Unix, e pure di una buona ingegneria del software, il che probabilmente spiega perchE si E 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 = )) { print $handle $linea; } } La funzione C nel blocco C del padre E lE per inviare un segnale al nostro processo figlio (che sta girando nel blocco C) 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 C 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 E molto efficiente (per fare un eufemismo), ma E il piE semplice da spiegare e funziona ragionevolmente bene. =head1 Server TCP con IO::Socket Come sempre, metter su un server E un po' piE complicato che lanciare un client. Il modello E che il server crea un particolare tipo di socket, che non fa altro che ascoltare su una porta particolare aspettando connessioni in ingresso. PuE farlo grazie al metodo C<< IO::Socket::INET->new() >>, con argomenti leggermente differenti rispetto al client. =over 4 =item Proto Indica il protocollo da utilizzare. Come per i nostri client, qui continueremo ad usare C<"tcp">. =item LocalPort Specifichiamo una porta locale nell'argomento C, 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 giE utilizzato", NdT]. Sotto Unix, il comando C vi mostrerE quali servizi sono al momento forniti da un programma server. =item Listen Il parametro C 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 C ha un simbolo speciale per il massimo valore di sistema, ossia C. =item Reuse Il parametro C E necessario per far sE che possiamo riavviare il nostro server a mano senza dover attendere qualche minuto per dare tempo ai buffer di sistema di liberarsi. =back Una volta che il socket generico del server E stato creato utilizzando i parametri indicati, il server si mette in attesa che un nuovo client si colleghi. Il server si blocca nel metodo C, che alla fine accetta una connessione bidirezionale dal client remoto. (Assicuratevi di chiamare C su questo I per aggirare il buffering). Per aggiungere semplicitE per l'utente, il nostro server chiede i comandi all'utente. La maggior parte dei server non lo fanno. PoichE inviamo la richiesta senza un carattere a-capo, dovrete utilizzare la variante C 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 piE 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; } =head1 UDP: Scambio di Messaggi Un altro tipo di impostazione client-server E quella che non utilizza connessioni, ma messggi. Le comunicazioni UDP richiedono molto meno sforzo, ma di contro forniscono anche una minore affidabilitE, poichE non ci sono garanzie che i messaggi arriveranno, men che meno che lo facciano in ordine ed integri. Nonostante ciE, UDP offre alcuni vantaggi rispetto al TCP, inclusa la possibilitE di inviare pacchetti in I [ossia, a tutti gli host in una rete, NdT] o in I [ossia, ad una molteplicitE di host, NdT] verso un bel mucchio di host di destinazione contemporaneamente (di solito nella vostra sottorete locale). Se avete problemi di affidabilitE e cominciate ad inserire controlli nel vostro sistema di messaggi, allora dovreste probabilmente utilizzare TCP. Osservate che i datagrammi UDP I costituiscono un flusso di ottetti e non dovrebbero essere trattati come tali. CiE rende l'utilizzo dei meccanismi di I/O con bufferizzazione interna come stdio (ad esempio C e compagnia bella) particolarmente bizzarro. Utilizzate C o, meglio, C, 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 controllerE asincronamente, simulando un I ed utilizzando C per effettuare un'attesa di I/O con timeout. Per fare qualcosa di simile con il TCP, dovreste utilizzare un I 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 piE importante alla base di un possibile fallimento E la congestione nelle code nell'host di invio se il numero di host nella lista E sufficientemente grande. =head1 IPC SysV Mentre l'IPC System V non E cosE ampiamente utilizzata come i socket, mantiene tuttavia alcuni utilizzi interessanti. In ogni caso, non potete utilizzare l'IPC SysV o la C Berkeley per avere memoria condivisa in modo da condividere una variabile fra piE processi. Questo accade perchE 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 piE di un processo. Chiamate il file F: # 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 piE di un processo. Chiamate il file F: # '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 E stato scritto tanto tempo fa, ed ha un'aria indiscutibilmente goffa. Per dargli un aspetto piE moderno, consultate il modulo C che E 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"; } =head1 NOTE La maggior parte delle funzioni presentate restituisce, silenziosamente ma educatamente, C 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 I lanciano C quando gli argomenti sono scorretti). E 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 I B<-T> alla riga C<#!> per i server: #!/usr/bin/perl -Tw use strict; use sigtrap; use Socket; =head1 BUG Tutte queste funzioni creano problemi di portabilitE specifici per i vari sistemi. Come giE osservato, Perl E alla mercE delle vostre librerie C per molta parte del suo comportamento di sistema. E probabilmente piE 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 possibilitE che il vostro codice sia portabile. =head1 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]. =head1 SI VEDA ANCHE C'E molto piE da interconnettersi di quanto avete visto, ma dovrebbe esservi sufficiente per partire. Per i programmatori intrepidi, il libro di testo indispensabile E I ["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 E lasciata come esercizio per il lettore. La pagina del manuale L descrive la libreria di oggetti, mentre quella L descrive l'interfaccia di basso livello ai socket. Oltre alle funzioni ovvie in L, dovreste anche controllare il file F nel sito CPAN piE vicino. (Consultate L o, meglio ancora, la F per una descrizione di cosa sia CPAN e di dove trovarlo). La sezione 5 del file F E 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. =head1 TRADUZIONE =head2 Versione La versione su cui si basa questa traduzione E ottenibile con: perl -MPOD2::IT -e print_pod perlipc Per maggiori informazioni sul progetto di traduzione in italiano si veda L. =head2 Traduttore Traduzione a cura di Flavio Poletti. =head2 Revisore Revisione a cura di dree. =cut