[PILLOLA] Comunicazione tramite rete (introduzione allo socket)
In questa prima parte introdurremo gli socket bloccanti, come si creano, si usano e come si distruggono.
Le comunicazioni tra due processi possono essere con connessione o senza connessione.
L'UDP:
Un protocollo senza connessione è l'UDP che non garantisce che il pacchetto sia giunto a destinazione. Possiamo paragonare questo protocollo ad una lettere inviata ad un amico della quale non sappiamo se è arrivata a destinazione.
Il TCP:
Il TCP è un protocollo bidirezionale garantito. Viene effettuata una connessione con un altro processo e se qualcosa va storto il protocollo ci avverte del problema. Possiamo immaginarlo come una telefonata e mentre parliamo il nostro interlocutore fa ogni tanto segno di essere ancora presente.
Ho accennato che parleremo di Socket "BLOCCANTI" che è una particolarità dei protocolli con connessione. Per ritornare all'esempio della telefonata bisogna immaginare che il nostro amico ci abbia chiesto di dettargli un esercizio di matematica perchè lui non lo ha. Noi iniziamo a dettare il testo. Non possiamo andare alla velocità che vogliamo ma bisogna aspettare che lui abbia finito di riscrivere quella parte per continuare. Il nostro amico non potrà mai memoriazzare un lungo esercizio a memoria e riscriverlo a fine conversazione.
Allo stesso modo il nostro amico non potrà scrivere prima che noi non abbiamo dettato la parte seguente dell'esercizio, quindi resterà in attessa.
Gli socket bloccanti funzionano alla stessa maniera. Restano in ascolto su uno socket fino a quando non si riceve qualcosa. Vedremo in seguito gli socket NON bloccanti, anche se servono in casi particolare come port scanner o creare dei P2p @_@
Una buona documentazione la trovate sulla guida ufficiale di perldoc su IO::Socket::INET
Server e Client trattano lo socket alla stessa maniera, sia per inviare i dati che per la ricezione, cambia solo l'inizializzazione.
Il client che tenta di effettuare una connessione al server X.Y.Z.T userà questo codice:
=Socket Client BLoccante TCP
-----------------------------------
my $sock = IO::Socket::INET->new (PeerAddr => 'www.gigi.com', # Anche l'IP va bennissimo
PeerPort => '1234', # La porta del server
Proto => 'tcp' # Se il protocollo è TCP si può anche omettere Proto
);
unless ($sock) { # Unless=Se la condizione è falsa esegui il blocco. Ossia se $sock ha valore nullo esegui.
print "La connessione non è avvenuta\n";
exit;
}
print $sock "Ciao server, come stai oggi?\n\r"; # La funzione print si usa anche per i file e gli socket.
my $tmphan=select $sock;
$|=1; # E' sempre meglio svuotare il buffer, per ora prendete questa istruzione come buona.
select $tmphan;
close $sock; # Chiude lo socket
-----------------------------------
=Socket Server Bloccante TCP
-----------------------------------
$server = IO::Socket::INET->new(Listen => 5,
LocalAddr => 'www.gigi.com', # Verranno accettate connessioni che hanno come
# destinazione l'ip corrispondete al dominio gigi.com
LocalPort => 1234, # La porta di ascolto
Proto => 'tcp'); # Protocollo usato
$alfa=$server->accept(); # Resta in attesa che il client tenti la connessione con gigi.com
# Seguita l'istruzione precedente avremo in $alfa l'oggetto socket che ci permette di comunicare con il client.
recv $alfa,$buf,1500,0; # Resta in attessa di ricezione di un pacchetto dal client
print "Il client mi ha spedito questa scritta;$buf\n";
close $alfa; # CHiude la comunicazione
----------------------------
Bene, la prima lezione è terminata. Consultate il man di IO::Socket e IO::Socket::INET per molti altri dettagli.
Su perlfunc trovate le giuste sintassi per la funzione send e recv.
Prossima lezione spegera come capire se in uno socket ci sono dei dati da analizzare senza mettersi in attessa.
ciao.
[PILLOLA][socket] recv non bloccante

Anallizzeremo ora la funzione select(RBITS,WBITS,EBITS,TIMEOUT) inclusa nel perl core e che quindi non necessita di alcuna librerie.
select prmette di selezionare alcuni filehandle e di capire quando essi accessibili in scrittura e in lettura.
Consideriamo l'oggetto socket $sock. Vogliamo sapere se ci sono dei dati in arrivo senza usare recv.
Ricordate che recv blocca il programma finche non ci sono dati in arrivo?
Il seguente ciclo si mette in loop fino a quando non ci sono dati nel buffer di $sock.
=Socket Ricezione non bloccante
--------------------------------------------
do {
$rin='';
vec($rin,fileno($sock),1) = 1;
} until (select ($rin,undef,$rin,0));
recv $sock,$buf,1500,0; # Dopo il primo ciclo ci saranno sicuramente dei dati.
---------------------------------------------
Nella chiamata select è possibile impostare come ultimo valore un tempo di timeout in secondi.
Nell'esempio era impostato a Zero, ossia controlla se ci sono dati ed esce subito.
Se avremo impostato un tempo di timeout di 10 avrebbe aspettato un tempo massimo di 10 secondi prima di uscire.
Aspetta come tempo massimo! Esce dalla chiamata select non appena gli socket selezionati avvertono un cambiamento.
E' possibile impostare come timeout anche 0.25 secondi, non solo valori interi.
La stessa funzione select si usa per limitare la banda in upload su uno socket non bloccante.
La limitazione della banda in upload (ad esempio per inviare un file) su uno socket si può limitare richiamando il modulo Benchmark e sfruttando sempre la chiamata select.
[PILLOLA][socket] Inivare file limitando la banda in uscita.
Se non l'avete installatevi il modulo Time::HiRes.
Partiamo subito con il codice:
=Socket
------------------------------------
use IO::Socket::INET;
use Time::HiRes;
my $cansend=0;
my $sock = IO::Socket::INET->new(Listen => 5,
LocalPort => 12345,
Proto => 'tcp');
my $data="questra frase di esempio mostrerà come è possibilile limitare la banda in upload su una connessione.

";
my $speed=4; # Byte al secondo.
print "in ascolto $sock\n";
$client=$sock->accept;
my $inviati=0;
my $lastsend=Time::HiRes::time();
my $rin='';
vec($rin,fileno($client),1) = 1;
select $client;
$speed=1/$speed;
while (select (undef,$rin,$rin,2)) {
$inviati+=Time::HiRes::time()-$lastsend;
$lastsend=Time::HiRes::time();
if ($cansend=int($inviati/$speed)) {
$inviati-=$speed*$cansend;
print substr($data,0,$cansend,"");
$|=1; # Svuota il buffer.
}
$rin='';
vec($rin,fileno($client),1) = 1;
}
-------------------------------------
Questo ciclo limita molto precisamente i byte in uscita.
Per provarlo eseguite lo script e con telnet connettetevi in localhost alla porta 12345.
Per vedere cosa fa substr, int, guardatevi la guida perlfunc, sono delle istruzioni basilari di perl.
Time::HiRes::time() ve lo dico io. Fa parte del modulo Time::HiRes e ritorna i secondi trascorsi dal 1970 con una precisione di un milionesimo di secondo.
Per il resto studiatevi il codice :P
Il ciclo funzione perfettamente anche se dovesse rimanere bloccato per qualche secondo alla fine del ciclo while.
Provate ad aggiungere uno sleep(3) prima dell'ultima graffa. Vedrete che invierà un certo gruppo di X byte tutti insieme.
Questo ciclo è eseguito migliaia di volte al secondo senza sleep e usa il massimo delle risorse, ecco perchè p2p come emule fanno il controllo sui buffer in uscita ogni 0.2 secondi (mi sembra). Come se dopo il controllo ci fosse uno sleep(0.2).