LordSaga640
13-01-2005, 15:33
Ho visto che sull'NG qualcuno chiede come gestire più socket insieme solo che si trova male con IO::Select.
Provo a rendere pubblica una mia libreria.
Non l'ho ancora testata affondo.
La libreria è multipiattaforma.
E' compresa anche una guida:
package IO::Socket::cTcp;
use IO::Socket;
use IO::Select;
use Compress::Zlib;
use Carp;
use Time::HiRes;
use Digest::CRC qw(crc32);
use strict;
=head1 NAME
IO::Socket::cTcp - Utilità per la gestione delle connessioni
=head1 SYNOPSIS
use IO::Socket::cTcp;
=head1 REQUIRE LIBRARY
use IO::Socket;
use IO::Select;
use Compress::Zlib;
use Carp;
use Time::HiRes;
use Digest::CRC;
=head1 DESCRIPTION
C<IO::Socket::cTcp> si trova ad un livello superiore rispetto a L<IO::Select>.
Può trovarsi utile nel caso si volessero usare connessioni multiple
con un controllo dei buffer e sulla banda in uscita.
=head1 CONSTRUCTOR
=over 4
=item new ( [ARGS] )
Crea un nuovo oggetto C<IO::Socket::cTcp> .
Gli argomenti sono opzionali.
Es: new (BandLimit=>1500); #Banda limite in upload impostato a 1500 byte al secondo (connessione generica)
=cut
sub new {
my ($class, %argv)=@_;
my $this={};
$this->{config}={};
$this->{config}->{BandLimit}=$argv{BandLimit} || "0";
$this->{IOS}={};
$this->{IOS}->{'read'}=IO::Select->new();
$this->{IOS}->{'write'}=IO::Select->new();
$this->{TimeOut}={};
$this->{TimeOut}->{Correct}=Cycle->new(0.1);
$this->{TimeOut}->{TryConn}=Cycle->new(0.3);
$this->{tosend}={};
$this->{tosend}->{Dedicata}={};
$this->{tosend}->{Comune}={};
$this->{TryConnTmp}={};
$this->{Socket}={};
return bless($this,$class);
}
=back
=head2 METHODS
=over 4
=item AddSock(socket,[Type,[BandLimit]])
Socket è l'oggetto socket che si intende gestire con la libreria cTcp.
"Type" specifica il tipo dello socket.
I tipi di dati possono essere "server,compdata,data";
|->data: Oggetto socket standard con un altro client
|->server: L'oggetto socket specificato viene gestito come server
e avviserà quando qualcuno tenta di connettersi su di esso.
|->compdata: Se specificato questo tipo anche l'altro client deve fare uso della medesima libreria cTcp.
Specificando questo tipo il pacchetto che si tenta di inviare viene compresso con zlib.
Oltre che l'invio di stringhe standard si possono inviare anche hash.
La sicurezza del protocollo non è garantita, è ancora alle prime ver.
bandlimite è la banda che si intende dedicare a questo socket.
Se non è specificato bandlimit, la banda generica usata nel costruttore viene
suddivisa tra gli socket che non hanno specificato bandlimit.
Se bandlimit è negativo è come assegnare a bandlimit un valore infinito.
=cut
sub AddSock {
my ($this, $sock, $type, $bandlimit)=@_;
return undef unless $sock;
return undef if exists %{$this->{Socket}}->{fileno $sock};
$type=$type || 'data';
$bandlimit=0 unless defined $bandlimit;
return undef if $type!~ /^server$|^compdata$|^data$/;
$this->{IOS}->{'read'}->add($sock);
my $socknum=fileno $sock;
$this->{Socket}->{"$socknum"}={};
$this->{Socket}->{"$socknum"}->{Type}=$type;
$this->{Socket}->{"$socknum"}->{BandLimit}=$bandlimit;
$this->{Socket}->{"$socknum"}->{RecvBuff}=[];
$this->{Socket}->{"$socknum"}->{Sock}=$sock;
print "lo socket aggiunto è ".$this->{Socket}->{"$socknum"}->{Type}."<->".fileno($sock)."-".$this->{Socket}->{"$socknum"}->{Sock}."\n";
return 1;
}
=item Select([timeout])
Il metodo Select deve essere chiamato costantemente.
Resta in ascolto di tutti gli socket impostati sull'oggetto cTcp per un tempo massimo impostato su "timeout".
Se timeout è undef il tempo di attesa è infinito fino a quando non accade
qualcosa in uno degli socket da tenere sotto controllo.
Il metodo Select torna un riferimento ad un hash. Ogni elemento dell'hash ha un riferimento ad un array do oggetti socket.
Le chiavi dell'hash che è ritornato dal metodo possono essere:
exception-La funzione Select di Io::Select ha tornato un exception, vedere Io::Select
ConnessioneFallita-Se avete tentato di creare una nuova connessione con il metodo TryConnect()
questo è uno dei valori che vi ritorna per le connessioni che dopo il timeout non sono ancora connesse.
ConnessioneRiuscita-Torna se avete tentato una connessione con TryConnect().
Contiene la lista degli socket correttamente connessi.
ConnectionRequest-Se state monitorando oggetti socket di tipo "server" questo valore vi può ritornare.
Contiene la lista di socket server sui quali c'è una richiesta di connessione.
Per ottenere lo socket in attesa di una connessione su quella porta basta prendere
lo socket server ritornato ed eseguire un $serverobj->accept();
CanRead-Sono disponibili dei dati in lettura su quello socket.
possiamo eseguire un $cTcpObj->recv($socket);
Nel caso la connessione sia un comdata la funzione recv può ritornare un riferimento ad un hash.
Disconnessi-Gli socket di tipo compdata e data disconnessi.
EmptyOutBuff-Quando i buffer in uscita sono stati correttamente inviati vengono inseriti in questa chiave.
Può essere utile nel caso si spediscano pacchetti molti grandi
e appena l'avete finito di inviare volete riempire nuovamente il buffer.
E' sempre una lista di socket.
=cut
sub Select {
my ($this, $timeout)=@_;
my $start=Time::HiRes::time();
my ($alfa, $beta,$gamma);
$this->{Return};
my ($timeout2,$timeout3)=($timeout,0);
LTIME: {
do {
(!defined($timeout)) ? ($timeout3=undef) : (($timeout2>0.20 || $timeout2<0) ? ($timeout3=0.20) : ($timeout3=$timeout2));
#print "Ciao $timeout2 $timeout3\n";
$this->_RefilBuff();
if (($alfa, $beta,$gamma)=IO::Select::select($this->{IOS}->{'read'},$this->{IOS}->{'write'},$this->{IOS}->{'read'},$timeout3)) {
$this->_reader($alfa) if @$alfa>0;
$this->_writer($beta) if @$beta>0;
$this->{Return}->{'exception'}=$gamma if scalar(@$gamma)>0;
}
$this->TryConnTimeout if $this->{TimeOut}->{TryConn}->check;
last LTIME if keys(%{$this->{Return}});
$timeout2=$timeout-(Time::HiRes::time()-$start) if defined($timeout);
last LTIME if $timeout2<0.001;
} while (!defined($timeout) || $timeout2>0);
}
$this->_RefilBuff();
my $return=$this->{Return};
$this->{Return}={};
(keys(%$return)) ? (return $return) : (return undef);
}
sub TryConnTimeout {
my $this=shift;
my ($sock);
foreach $sock ($this->{IOS}->{'write'}->handles) {
if ($this->{TryConnTmp}->{$sock}<Time::HiRes::time()) {
delete %{$this->{TryConnTmp}}->{$sock};
_addItem($this->{Return}, 'ConnessioneFallita', $sock);
$this->{IOS}->{'write'}->remove($sock);
}
}
}
sub _writer {
my ($this, $writer) =@_;
my ($buf,$fno);
foreach $buf (@$writer) {
_addItem($this->{Return}, 'ConnessioneRiuscita', $buf);
$this->{IOS}->{'write'}->remove($buf);
delete %{$this->{TryConnTmp}}->{$buf};
}
}
=item TryConnect(Ip or Host, porta,[timeout])
Tenta di effettura una connessione con l'ip e porta specificata.
I risultati sono ritornati dalla funzione Select.
Il metodo torna l'oggetto socket Non-Bloccante di quella connessione.
=cut
sub TryConnect {
my ($this,$ip, $port, $timeout)=@_;
$timeout=10 unless defined $timeout;
my $SockTest = IO::Socket::INET->new(Proto => 'tcp');
my $ict_set="1";
ioctl ($SockTest,2147772030, $ict_set);
return undef unless $ip=inet_aton $ip;
$SockTest->connect($port, $ip);
$this->{IOS}->{'write'}->add($SockTest);
$this->{TryConnTmp}->{$SockTest}=Time::HiRes::time()+$timeout;
#print "mi connetto a $ip , $port, $timeout , $SockTest\n";
return $SockTest;
}
sub _reader {
my ($this, $reader)=@_;
my $buf;
foreach $buf (@$reader) {
($this->{Socket}->{fileno $buf}->{Type} eq 'server') ?
_addItem($this->{Return}, 'ConnectionRequest', $buf) :
$this->_recv($this->{Socket}->{fileno $buf},$buf,$this->{Return});
}
}
sub _addItem {
my ($ref, $name, $ogg)=@_;
$ref->{$name}=[] unless exists %$ref->{$name};
push(@{$ref->{$name}}, $ogg);
}
sub _addItems {
my ($this, $name, @items)=@_;
print "$name aggiunge ",scalar(@items),"\n";
return undef if scalar(@items)>0;
$this->{Return}->{$name}=[] unless exists %{$this->{Return}}->{$name};
push(@{$this->{Return}->{$name}}, @items);
}
sub _recv {
my ($this,$objsock, $sock, $return)=@_;
my $temp;
recv($sock,$temp,9000,0);
if ($temp) {
_addItem($return, 'CanRead', $sock) if (_bufferizza($objsock,$temp));
} else {
_addItem($return, 'Disconnessi', $sock);
$this->Remove($sock);
}
}
sub _bufferizza {
my ($objsock, $temp)=@_;
$objsock->{TmpRecvBuff}.=$temp;
print "RICEVO ".$objsock->{Type}." $temp\n";
return 1 if ($objsock->{Type} eq 'data');
unless ($objsock->{Header}) {
return undef if (length($objsock->{TmpRecvBuff}) <11);
my %header;
@header{'proto','dim','compression','hash','crc'}= unpack("CICCI",substr($objsock->{TmpRecvBuff},0,11,""));
print "deve arrivare ".$header{'dim'}." byte\n";
return undef if $header{'tipo'}==155;
$objsock->{Header}=1;
$objsock->{DataHeader}=\%header;
}
print "final dim e current ".$objsock->{DataHeader}->{dim}."<->".length($objsock->{TmpRecvBuff})."\n";
return undef if length($objsock->{TmpRecvBuff}) < $objsock->{DataHeader}->{dim};
print "primo passo ci siamo".$objsock->{DataHeader}->{proto}."\n";
$objsock->{Header}=0;
my $data=substr($objsock->{TmpRecvBuff},0,$objsock->{DataHeader}->{dim},"");
return undef if $objsock->{DataHeader}->{proto} != 154;
return undef if crc32($data) != $objsock->{DataHeader}->{crc};
$data=uncompress($data) if $objsock->{DataHeader}->{'compression'};
return undef unless $data;
$data=BinDump::MainDeDump($data) if $objsock->{DataHeader}->{'hash'};
return undef unless $data;
push (@{$objsock->{RecvBuff}}, $data);
print "pacchetto speciale finito di ricevere\n";
my $spnum=_bufferizza($objsock) if length($objsock->{TmpRecvBuff});
return 1+$spnum;
}
=item send(socket,Datas,[Compressed])
socket è lo socket al quale si intendono inviare i dati.
Datas è una stringa o un riferimento ad un HASH (hashref).
Attenzione! Può essere solo un hashref solo nel caso che il tipo dello socket specificato sia 'compdata'.
Compressed è un valore boleano, ha significato solo se il tipo è 'compdata'.
Se è TRUE forza la NON compressione. Di standard viene spedito compresso un pacchetto compdata.
=cut
Segue nel messaggio dopo.
Non potevo inserire l'intero modulo in un solo messaggio.
Quando lo salvate , inserite il codice in un solo file.
Provo a rendere pubblica una mia libreria.
Non l'ho ancora testata affondo.
La libreria è multipiattaforma.
E' compresa anche una guida:
package IO::Socket::cTcp;
use IO::Socket;
use IO::Select;
use Compress::Zlib;
use Carp;
use Time::HiRes;
use Digest::CRC qw(crc32);
use strict;
=head1 NAME
IO::Socket::cTcp - Utilità per la gestione delle connessioni
=head1 SYNOPSIS
use IO::Socket::cTcp;
=head1 REQUIRE LIBRARY
use IO::Socket;
use IO::Select;
use Compress::Zlib;
use Carp;
use Time::HiRes;
use Digest::CRC;
=head1 DESCRIPTION
C<IO::Socket::cTcp> si trova ad un livello superiore rispetto a L<IO::Select>.
Può trovarsi utile nel caso si volessero usare connessioni multiple
con un controllo dei buffer e sulla banda in uscita.
=head1 CONSTRUCTOR
=over 4
=item new ( [ARGS] )
Crea un nuovo oggetto C<IO::Socket::cTcp> .
Gli argomenti sono opzionali.
Es: new (BandLimit=>1500); #Banda limite in upload impostato a 1500 byte al secondo (connessione generica)
=cut
sub new {
my ($class, %argv)=@_;
my $this={};
$this->{config}={};
$this->{config}->{BandLimit}=$argv{BandLimit} || "0";
$this->{IOS}={};
$this->{IOS}->{'read'}=IO::Select->new();
$this->{IOS}->{'write'}=IO::Select->new();
$this->{TimeOut}={};
$this->{TimeOut}->{Correct}=Cycle->new(0.1);
$this->{TimeOut}->{TryConn}=Cycle->new(0.3);
$this->{tosend}={};
$this->{tosend}->{Dedicata}={};
$this->{tosend}->{Comune}={};
$this->{TryConnTmp}={};
$this->{Socket}={};
return bless($this,$class);
}
=back
=head2 METHODS
=over 4
=item AddSock(socket,[Type,[BandLimit]])
Socket è l'oggetto socket che si intende gestire con la libreria cTcp.
"Type" specifica il tipo dello socket.
I tipi di dati possono essere "server,compdata,data";
|->data: Oggetto socket standard con un altro client
|->server: L'oggetto socket specificato viene gestito come server
e avviserà quando qualcuno tenta di connettersi su di esso.
|->compdata: Se specificato questo tipo anche l'altro client deve fare uso della medesima libreria cTcp.
Specificando questo tipo il pacchetto che si tenta di inviare viene compresso con zlib.
Oltre che l'invio di stringhe standard si possono inviare anche hash.
La sicurezza del protocollo non è garantita, è ancora alle prime ver.
bandlimite è la banda che si intende dedicare a questo socket.
Se non è specificato bandlimit, la banda generica usata nel costruttore viene
suddivisa tra gli socket che non hanno specificato bandlimit.
Se bandlimit è negativo è come assegnare a bandlimit un valore infinito.
=cut
sub AddSock {
my ($this, $sock, $type, $bandlimit)=@_;
return undef unless $sock;
return undef if exists %{$this->{Socket}}->{fileno $sock};
$type=$type || 'data';
$bandlimit=0 unless defined $bandlimit;
return undef if $type!~ /^server$|^compdata$|^data$/;
$this->{IOS}->{'read'}->add($sock);
my $socknum=fileno $sock;
$this->{Socket}->{"$socknum"}={};
$this->{Socket}->{"$socknum"}->{Type}=$type;
$this->{Socket}->{"$socknum"}->{BandLimit}=$bandlimit;
$this->{Socket}->{"$socknum"}->{RecvBuff}=[];
$this->{Socket}->{"$socknum"}->{Sock}=$sock;
print "lo socket aggiunto è ".$this->{Socket}->{"$socknum"}->{Type}."<->".fileno($sock)."-".$this->{Socket}->{"$socknum"}->{Sock}."\n";
return 1;
}
=item Select([timeout])
Il metodo Select deve essere chiamato costantemente.
Resta in ascolto di tutti gli socket impostati sull'oggetto cTcp per un tempo massimo impostato su "timeout".
Se timeout è undef il tempo di attesa è infinito fino a quando non accade
qualcosa in uno degli socket da tenere sotto controllo.
Il metodo Select torna un riferimento ad un hash. Ogni elemento dell'hash ha un riferimento ad un array do oggetti socket.
Le chiavi dell'hash che è ritornato dal metodo possono essere:
exception-La funzione Select di Io::Select ha tornato un exception, vedere Io::Select
ConnessioneFallita-Se avete tentato di creare una nuova connessione con il metodo TryConnect()
questo è uno dei valori che vi ritorna per le connessioni che dopo il timeout non sono ancora connesse.
ConnessioneRiuscita-Torna se avete tentato una connessione con TryConnect().
Contiene la lista degli socket correttamente connessi.
ConnectionRequest-Se state monitorando oggetti socket di tipo "server" questo valore vi può ritornare.
Contiene la lista di socket server sui quali c'è una richiesta di connessione.
Per ottenere lo socket in attesa di una connessione su quella porta basta prendere
lo socket server ritornato ed eseguire un $serverobj->accept();
CanRead-Sono disponibili dei dati in lettura su quello socket.
possiamo eseguire un $cTcpObj->recv($socket);
Nel caso la connessione sia un comdata la funzione recv può ritornare un riferimento ad un hash.
Disconnessi-Gli socket di tipo compdata e data disconnessi.
EmptyOutBuff-Quando i buffer in uscita sono stati correttamente inviati vengono inseriti in questa chiave.
Può essere utile nel caso si spediscano pacchetti molti grandi
e appena l'avete finito di inviare volete riempire nuovamente il buffer.
E' sempre una lista di socket.
=cut
sub Select {
my ($this, $timeout)=@_;
my $start=Time::HiRes::time();
my ($alfa, $beta,$gamma);
$this->{Return};
my ($timeout2,$timeout3)=($timeout,0);
LTIME: {
do {
(!defined($timeout)) ? ($timeout3=undef) : (($timeout2>0.20 || $timeout2<0) ? ($timeout3=0.20) : ($timeout3=$timeout2));
#print "Ciao $timeout2 $timeout3\n";
$this->_RefilBuff();
if (($alfa, $beta,$gamma)=IO::Select::select($this->{IOS}->{'read'},$this->{IOS}->{'write'},$this->{IOS}->{'read'},$timeout3)) {
$this->_reader($alfa) if @$alfa>0;
$this->_writer($beta) if @$beta>0;
$this->{Return}->{'exception'}=$gamma if scalar(@$gamma)>0;
}
$this->TryConnTimeout if $this->{TimeOut}->{TryConn}->check;
last LTIME if keys(%{$this->{Return}});
$timeout2=$timeout-(Time::HiRes::time()-$start) if defined($timeout);
last LTIME if $timeout2<0.001;
} while (!defined($timeout) || $timeout2>0);
}
$this->_RefilBuff();
my $return=$this->{Return};
$this->{Return}={};
(keys(%$return)) ? (return $return) : (return undef);
}
sub TryConnTimeout {
my $this=shift;
my ($sock);
foreach $sock ($this->{IOS}->{'write'}->handles) {
if ($this->{TryConnTmp}->{$sock}<Time::HiRes::time()) {
delete %{$this->{TryConnTmp}}->{$sock};
_addItem($this->{Return}, 'ConnessioneFallita', $sock);
$this->{IOS}->{'write'}->remove($sock);
}
}
}
sub _writer {
my ($this, $writer) =@_;
my ($buf,$fno);
foreach $buf (@$writer) {
_addItem($this->{Return}, 'ConnessioneRiuscita', $buf);
$this->{IOS}->{'write'}->remove($buf);
delete %{$this->{TryConnTmp}}->{$buf};
}
}
=item TryConnect(Ip or Host, porta,[timeout])
Tenta di effettura una connessione con l'ip e porta specificata.
I risultati sono ritornati dalla funzione Select.
Il metodo torna l'oggetto socket Non-Bloccante di quella connessione.
=cut
sub TryConnect {
my ($this,$ip, $port, $timeout)=@_;
$timeout=10 unless defined $timeout;
my $SockTest = IO::Socket::INET->new(Proto => 'tcp');
my $ict_set="1";
ioctl ($SockTest,2147772030, $ict_set);
return undef unless $ip=inet_aton $ip;
$SockTest->connect($port, $ip);
$this->{IOS}->{'write'}->add($SockTest);
$this->{TryConnTmp}->{$SockTest}=Time::HiRes::time()+$timeout;
#print "mi connetto a $ip , $port, $timeout , $SockTest\n";
return $SockTest;
}
sub _reader {
my ($this, $reader)=@_;
my $buf;
foreach $buf (@$reader) {
($this->{Socket}->{fileno $buf}->{Type} eq 'server') ?
_addItem($this->{Return}, 'ConnectionRequest', $buf) :
$this->_recv($this->{Socket}->{fileno $buf},$buf,$this->{Return});
}
}
sub _addItem {
my ($ref, $name, $ogg)=@_;
$ref->{$name}=[] unless exists %$ref->{$name};
push(@{$ref->{$name}}, $ogg);
}
sub _addItems {
my ($this, $name, @items)=@_;
print "$name aggiunge ",scalar(@items),"\n";
return undef if scalar(@items)>0;
$this->{Return}->{$name}=[] unless exists %{$this->{Return}}->{$name};
push(@{$this->{Return}->{$name}}, @items);
}
sub _recv {
my ($this,$objsock, $sock, $return)=@_;
my $temp;
recv($sock,$temp,9000,0);
if ($temp) {
_addItem($return, 'CanRead', $sock) if (_bufferizza($objsock,$temp));
} else {
_addItem($return, 'Disconnessi', $sock);
$this->Remove($sock);
}
}
sub _bufferizza {
my ($objsock, $temp)=@_;
$objsock->{TmpRecvBuff}.=$temp;
print "RICEVO ".$objsock->{Type}." $temp\n";
return 1 if ($objsock->{Type} eq 'data');
unless ($objsock->{Header}) {
return undef if (length($objsock->{TmpRecvBuff}) <11);
my %header;
@header{'proto','dim','compression','hash','crc'}= unpack("CICCI",substr($objsock->{TmpRecvBuff},0,11,""));
print "deve arrivare ".$header{'dim'}." byte\n";
return undef if $header{'tipo'}==155;
$objsock->{Header}=1;
$objsock->{DataHeader}=\%header;
}
print "final dim e current ".$objsock->{DataHeader}->{dim}."<->".length($objsock->{TmpRecvBuff})."\n";
return undef if length($objsock->{TmpRecvBuff}) < $objsock->{DataHeader}->{dim};
print "primo passo ci siamo".$objsock->{DataHeader}->{proto}."\n";
$objsock->{Header}=0;
my $data=substr($objsock->{TmpRecvBuff},0,$objsock->{DataHeader}->{dim},"");
return undef if $objsock->{DataHeader}->{proto} != 154;
return undef if crc32($data) != $objsock->{DataHeader}->{crc};
$data=uncompress($data) if $objsock->{DataHeader}->{'compression'};
return undef unless $data;
$data=BinDump::MainDeDump($data) if $objsock->{DataHeader}->{'hash'};
return undef unless $data;
push (@{$objsock->{RecvBuff}}, $data);
print "pacchetto speciale finito di ricevere\n";
my $spnum=_bufferizza($objsock) if length($objsock->{TmpRecvBuff});
return 1+$spnum;
}
=item send(socket,Datas,[Compressed])
socket è lo socket al quale si intendono inviare i dati.
Datas è una stringa o un riferimento ad un HASH (hashref).
Attenzione! Può essere solo un hashref solo nel caso che il tipo dello socket specificato sia 'compdata'.
Compressed è un valore boleano, ha significato solo se il tipo è 'compdata'.
Se è TRUE forza la NON compressione. Di standard viene spedito compresso un pacchetto compdata.
=cut
Segue nel messaggio dopo.
Non potevo inserire l'intero modulo in un solo messaggio.
Quando lo salvate , inserite il codice in un solo file.