X-Git-Url: http://dxcluster.net/gitweb/gitweb.cgi?a=blobdiff_plain;f=perl%2FMsg.pm;h=5aebab650caac475f3b05600ff2a2e7e3d46f35b;hb=d17f05b19fac36a0a8a2f828912a1a7ebefae79f;hp=65a32a662c64e7e1ec82df62d2bc94ba19cd848f;hpb=b4826d1f4125788e14fed3adbb99e66242904e74;p=spider.git diff --git a/perl/Msg.pm b/perl/Msg.pm index 65a32a66..5aebab65 100644 --- a/perl/Msg.pm +++ b/perl/Msg.pm @@ -5,98 +5,364 @@ # # I have modified it to suit my devious purposes (Dirk Koopman G1TLH) # -# $Id$ +# # package Msg; -require Exporter; -@ISA = qw(Exporter); - use strict; -use IO::Select; -use IO::Socket; -use Carp; -use vars qw (%rd_callbacks %wt_callbacks $rd_handles $wt_handles); +use DXUtil; + +use DXDebug; +use Timer; + +use vars qw(%rd_callbacks %wt_callbacks %er_callbacks $rd_handles $wt_handles $er_handles $now %conns $noconns $blocking_supported $cnum $total_in $total_out $io_socket); %rd_callbacks = (); %wt_callbacks = (); +%er_callbacks = (); $rd_handles = IO::Select->new(); $wt_handles = IO::Select->new(); -my $blocking_supported = 0; +$er_handles = IO::Select->new(); +$total_in = $total_out = 0; + +$now = time; BEGIN { # Checks if blocking is supported eval { - require POSIX; POSIX->import(qw (F_SETFL O_NONBLOCK EAGAIN)); + local $^W; + require POSIX; POSIX->import(qw(O_NONBLOCK F_SETFL F_GETFL)) + }; + + eval { + local $^W; + require IO::Socket::INET6; + }; + + if ($@) { + dbg($@); + require IO::Socket; + $io_socket = 'IO::Socket::INET'; + } else { + $io_socket = 'IO::Socket::INET6'; + } + $io_socket->import; + + if ($@ || $main::is_win) { + $blocking_supported = $io_socket->can('blocking') ? 2 : 0; + } else { + $blocking_supported = $io_socket->can('blocking') ? 2 : 1; + } + + + # import as many of these errno values as are available + eval { + local $^W; + require Errno; Errno->import(qw(EAGAIN EINPROGRESS EWOULDBLOCK)); + }; + + unless ($^O eq 'MSWin32') { + if ($] >= 5.6) { + eval { + local $^W; + require Socket; Socket->import(qw(IPPROTO_TCP TCP_NODELAY)); + }; + } else { + dbg("IPPROTO_TCP and TCP_NODELAY manually defined"); + eval 'sub IPPROTO_TCP { 6 };'; + eval 'sub TCP_NODELAY { 1 };'; + } + } + # http://support.microsoft.com/support/kb/articles/Q150/5/37.asp + # defines EINPROGRESS as 10035. We provide it here because some + # Win32 users report POSIX::EINPROGRESS is not vendor-supported. + if ($^O eq 'MSWin32') { + eval '*EINPROGRESS = sub { 10036 };' unless defined *EINPROGRESS; + eval '*EWOULDBLOCK = *EAGAIN = sub { 10035 };' unless defined *EWOULDBLOCK; + eval '*F_GETFL = sub { 0 };' unless defined *F_GETFL; + eval '*F_SETFL = sub { 0 };' unless defined *F_SETFL; + eval 'sub IPPROTO_TCP { 6 };'; + eval 'sub TCP_NODELAY { 1 };'; + $blocking_supported = 0; # it appears that this DOESN'T work :-( + } +} + +my $w = $^W; +$^W = 0; +my $eagain = eval {EAGAIN()}; +my $einprogress = eval {EINPROGRESS()}; +my $ewouldblock = eval {EWOULDBLOCK()}; +$^W = $w; +$cnum = 0; + + +# +#----------------------------------------------------------------- +# Generalised initializer + +sub new +{ + my ($pkg, $rproc) = @_; + my $obj = ref($pkg); + my $class = $obj || $pkg; + + my $conn = { + rproc => $rproc, + inqueue => [], + outqueue => [], + state => 0, + lineend => "\r\n", + csort => 'telnet', + timeval => 60, + blocking => 0, + cnum => (($cnum < 999) ? (++$cnum) : ($cnum = 1)), }; - $blocking_supported = 1 unless $@; + + $noconns++; + + dbg("Connection created ($noconns)") if isdbg('connll'); + return bless $conn, $class; +} + +sub set_error +{ + my $conn = shift; + my $callback = shift; + $conn->{eproc} = $callback; + set_event_handler($conn->{sock}, error => $callback) if exists $conn->{sock}; +} + +sub set_rproc +{ + my $conn = shift; + my $callback = shift; + $conn->{rproc} = $callback; +} + +sub blocking +{ + return unless $blocking_supported; + + # Make the handle stop blocking, the Windows way. + if ($blocking_supported) { + $_[0]->blocking($_[1]); + } else { + my $flags = fcntl ($_[0], F_GETFL, 0); + if ($_[1]) { + $flags &= ~O_NONBLOCK; + } else { + $flags |= O_NONBLOCK; + } + fcntl ($_[0], F_SETFL, $flags); + } +} + +# save it +sub conns +{ + my $pkg = shift; + my $call = shift; + my $ref; + + if (ref $pkg) { + $call = $pkg->{call} unless $call; + return undef unless $call; + dbg("changing $pkg->{call} to $call") if isdbg('connll') && exists $pkg->{call} && $call ne $pkg->{call}; + delete $conns{$pkg->{call}} if exists $pkg->{call} && exists $conns{$pkg->{call}} && $pkg->{call} ne $call; + $pkg->{call} = $call; + $ref = $conns{$call} = $pkg; + dbg("Connection $pkg->{cnum} $call stored") if isdbg('connll'); + } else { + $ref = $conns{$call}; + } + return $ref; +} + +# this is only called by any dependent processes going away unexpectedly +sub pid_gone +{ + my ($pkg, $pid) = @_; + + my @pid = grep {$_->{pid} == $pid} values %conns; + foreach my $p (@pid) { + &{$p->{eproc}}($p, "$pid has gorn") if exists $p->{eproc}; + $p->disconnect; + } +} + +sub ax25 +{ + my $conn = shift; + return $conn->{csort} eq 'ax25'; +} + +sub peerhost +{ + my $conn = shift; + $conn->{peerhost} ||= 'ax25' if $conn->ax25; + $conn->{peerhost} ||= $conn->{sock}->peerhost if $conn->{sock} && $conn->{sock}->isa('IO::Socket::INET'); + $conn->{peerhost} ||= 'UNKNOWN'; + return $conn->{peerhost}; } #----------------------------------------------------------------- # Send side routines sub connect { - my ($pkg, $to_host, $to_port,$rcvd_notification_proc) = @_; - - # Create a new internet socket - - my $sock = IO::Socket::INET->new ( - PeerAddr => $to_host, - PeerPort => $to_port, - Proto => 'tcp', - Reuse => 1); - - return undef unless $sock; + my ($pkg, $to_host, $to_port, $rproc) = @_; # Create a connection end-point object - my $conn = { - sock => $sock, - rcvd_notification_proc => $rcvd_notification_proc, - }; - - if ($rcvd_notification_proc) { - my $callback = sub {_rcv($conn, 0)}; - set_event_handler ($sock, "read" => $callback); + my $conn = $pkg; + unless (ref $pkg) { + $conn = $pkg->new($rproc); + } + $conn->{peerhost} = $to_host; + $conn->{peerport} = $to_port; + $conn->{sort} = 'Outgoing'; + + my $sock; + if ($blocking_supported) { + $sock = $io_socket->new(PeerAddr => $to_host, PeerPort => $to_port, Proto => 'tcp', Blocking =>0) or return undef; + } else { + # Create a new internet socket + $sock = $io_socket->new(); + return undef unless $sock; + + my $proto = getprotobyname('tcp'); + $sock->socket(AF_INET, SOCK_STREAM, $proto) or return undef; + + blocking($sock, 0); + $conn->{blocking} = 0; + + # does the host resolve? + my $ip = gethostbyname($to_host); + return undef unless $ip; + + my $r = connect($sock, pack_sockaddr_in($to_port, $ip)); + return undef unless $r || _err_will_block($!); + } + + $conn->{sock} = $sock; + $conn->{peerhost} = $sock->peerhost; # for consistency + + if ($conn->{rproc}) { + my $callback = sub {$conn->_rcv}; + set_event_handler ($sock, read => $callback); } - return bless $conn, $pkg; + return $conn; +} + +sub start_program +{ + my ($conn, $line, $sort) = @_; + my $pid; + + local $^F = 10000; # make sure it ain't closed on exec + my ($a, $b) = $io_socket->socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC); + if ($a && $b) { + $a->autoflush(1); + $b->autoflush(1); + $pid = fork; + if (defined $pid) { + if ($pid) { + close $b; + $conn->{sock} = $a; + $conn->{csort} = $sort; + $conn->{lineend} = "\cM" if $sort eq 'ax25'; + $conn->{pid} = $pid; + if ($conn->{rproc}) { + my $callback = sub {$conn->_rcv}; + Msg::set_event_handler ($a, read => $callback); + } + dbg("connect $conn->{cnum}: started pid: $conn->{pid} as $line") if isdbg('connect'); + } else { + $^W = 0; + dbgclose(); + STDIN->close; + STDOUT->close; + STDOUT->close; + *STDIN = IO::File->new_from_fd($b, 'r') or die; + *STDOUT = IO::File->new_from_fd($b, 'w') or die; + *STDERR = IO::File->new_from_fd($b, 'w') or die; + close $a; + unless ($main::is_win) { + # $SIG{HUP} = 'IGNORE'; + $SIG{HUP} = $SIG{CHLD} = $SIG{TERM} = $SIG{INT} = 'DEFAULT'; + alarm(0); + } + exec "$line" or dbg("exec '$line' failed $!"); + } + } else { + dbg("cannot fork for $line"); + } + } else { + dbg("no socket pair $! for $line"); + } + return $pid; } -sub disconnect { +sub disconnect +{ my $conn = shift; + return if exists $conn->{disconnecting}; + + $conn->{disconnecting} = 1; my $sock = delete $conn->{sock}; - return unless defined($sock); - set_event_handler ($sock, "read" => undef, "write" => undef); - shutdown($sock, 3); + $conn->{state} = 'E'; + $conn->{timeout}->del if $conn->{timeout}; + + # be careful to delete the correct one + my $call; + if ($call = $conn->{call}) { + my $ref = $conns{$call}; + delete $conns{$call} if $ref && $ref == $conn; + } + $call ||= 'unallocated'; + dbg("Connection $conn->{cnum} $call disconnected") if isdbg('connll'); + + # get rid of any references + for (keys %$conn) { + if (ref($conn->{$_})) { + delete $conn->{$_}; + } + } + + if (defined($sock)) { + set_event_handler ($sock, read => undef, write => undef, error => undef); + shutdown($sock, 2); + close($sock); + } + + unless ($main::is_win) { + kill 'TERM', $conn->{pid} if exists $conn->{pid}; + } } sub send_now { my ($conn, $msg) = @_; - _enqueue ($conn, $msg); + $conn->enqueue($msg); $conn->_send (1); # 1 ==> flush } sub send_later { my ($conn, $msg) = @_; - _enqueue($conn, $msg); + $conn->enqueue($msg); my $sock = $conn->{sock}; return unless defined($sock); - set_event_handler ($sock, "write" => sub {$conn->_send(0)}); + set_event_handler ($sock, write => sub {$conn->_send(0)}); } -sub _enqueue { - my ($conn, $msg) = @_; - # prepend length (encoded as network long) - my $len = length($msg); - $msg = pack ('N', $len) . $msg; - push (@{$conn->{queue}}, $msg); +sub enqueue { + my $conn = shift; + push (@{$conn->{outqueue}}, defined $_[0] ? $_[0] : ''); } sub _send { my ($conn, $flush) = @_; my $sock = $conn->{sock}; return unless defined($sock); - my ($rq) = $conn->{queue}; + my $rq = $conn->{outqueue}; # If $flush is set, set the socket to blocking, and send all # messages in the queue - return only if there's an error @@ -104,14 +370,19 @@ sub _send { # return to the event loop only after every message, or if it # is likely to block in the middle of a message. - $flush ? $conn->set_blocking() : $conn->set_non_blocking(); +# if ($conn->{blocking} != $flush) { +# blocking($sock, $flush); +# $conn->{blocking} = $flush; +# } my $offset = (exists $conn->{send_offset}) ? $conn->{send_offset} : 0; while (@$rq) { my $msg = $rq->[0]; - my $bytes_to_write = length($msg) - $offset; + my $mlth = length($msg); + my $bytes_to_write = $mlth - $offset; my $bytes_written = 0; - while ($bytes_to_write) { + confess("Negative Length! msg: '$msg' lth: $mlth offset: $offset") if $bytes_to_write < 0; + while ($bytes_to_write > 0) { $bytes_written = syswrite ($sock, $msg, $bytes_to_write, $offset); if (!defined($bytes_written)) { @@ -123,160 +394,235 @@ sub _send { # be called back eventually, and will resume sending return 1; } else { # Uh, oh - $conn->handle_send_err($!); + &{$conn->{eproc}}($conn, $!) if exists $conn->{eproc}; + $conn->disconnect; return 0; # fail. Message remains in queue .. } - } + } elsif (isdbg('raw')) { + my $call = $conn->{call} || 'none'; + dbgdump('raw', "$call send $bytes_written: ", $msg); + } + $total_out += $bytes_written; $offset += $bytes_written; $bytes_to_write -= $bytes_written; } delete $conn->{send_offset}; $offset = 0; shift @$rq; - last unless $flush; # Go back to select and wait + #last unless $flush; # Go back to select and wait # for it to fire again. } # Call me back if queue has not been drained. - if (@$rq) { - set_event_handler ($sock, "write" => sub {$conn->_send(0)}); - } else { - set_event_handler ($sock, "write" => undef); + unless (@$rq) { + set_event_handler ($sock, write => undef); + if (exists $conn->{close_on_empty}) { + &{$conn->{eproc}}($conn, undef) if exists $conn->{eproc}; + $conn->disconnect; + } } 1; # Success } -sub _err_will_block { - if ($blocking_supported) { - return ($_[0] == EAGAIN()); - } - return 0; -} -sub set_non_blocking { # $conn->set_blocking - if ($blocking_supported) { - # preserve other fcntl flags - my $flags = fcntl ($_[0], F_GETFL(), 0); - fcntl ($_[0], F_SETFL(), $flags | O_NONBLOCK()); - } +sub dup_sock +{ + my $conn = shift; + my $oldsock = $conn->{sock}; + my $rc = $rd_callbacks{$oldsock}; + my $wc = $wt_callbacks{$oldsock}; + my $ec = $er_callbacks{$oldsock}; + my $sock = $oldsock->new_from_fd($oldsock, "w+"); + if ($sock) { + set_event_handler($oldsock, read=>undef, write=>undef, error=>undef); + $conn->{sock} = $sock; + set_event_handler($sock, read=>$rc, write=>$wc, error=>$ec); + $oldsock->close; + } } -sub set_blocking { - if ($blocking_supported) { - my $flags = fcntl ($_[0], F_GETFL(), 0); - $flags &= ~O_NONBLOCK(); # Clear blocking, but preserve other flags - fcntl ($_[0], F_SETFL(), $flags); - } + +sub _err_will_block { + return 0 unless $blocking_supported; + return ($_[0] == $eagain || $_[0] == $ewouldblock || $_[0] == $einprogress); } -sub handle_send_err { - # For more meaningful handling of send errors, subclass Msg and - # rebless $conn. - my ($conn, $err_msg) = @_; - warn "Error while sending: $err_msg \n"; - set_event_handler ($conn->{sock}, "write" => undef); + +sub close_on_empty +{ + my $conn = shift; + $conn->{close_on_empty} = 1; } #----------------------------------------------------------------- # Receive side routines -my ($g_login_proc,$g_pkg); -my $main_socket = 0; sub new_server { - @_ == 4 || die "Msg->new_server (myhost, myport, login_proc)\n"; + @_ == 4 || die "Msg->new_server (myhost, myport, login_proc\n"; my ($pkg, $my_host, $my_port, $login_proc) = @_; - - $main_socket = IO::Socket::INET->new ( - LocalAddr => $my_host, - LocalPort => $my_port, - Listen => 5, + my $self = $pkg->new($login_proc); + + $self->{sock} = $io_socket->new ( + LocalAddr => "$my_host:$my_port", +# LocalPort => $my_port, + Listen => SOMAXCONN, Proto => 'tcp', - Reuse => 1); - die "Could not create socket: $! \n" unless $main_socket; - set_event_handler ($main_socket, "read" => \&_new_client); - $g_login_proc = $login_proc; $g_pkg = $pkg; + Reuse => 1); + die "Could not create socket: $! \n" unless $self->{sock}; + set_event_handler ($self->{sock}, read => sub { $self->new_client } ); + return $self; +} + + +sub nolinger +{ + my $conn = shift; + + unless ($main::is_win) { + if (isdbg('sock')) { + my ($l, $t) = unpack "ll", getsockopt($conn->{sock}, SOL_SOCKET, SO_LINGER); + my $k = unpack 'l', getsockopt($conn->{sock}, SOL_SOCKET, SO_KEEPALIVE); + my $n = $main::is_win ? 0 : unpack "l", getsockopt($conn->{sock}, IPPROTO_TCP, TCP_NODELAY); + dbg("Linger is: $l $t, keepalive: $k, nagle: $n"); + } + + eval {setsockopt($conn->{sock}, SOL_SOCKET, SO_KEEPALIVE, 1)} or dbg("setsockopt keepalive: $!"); + eval {setsockopt($conn->{sock}, SOL_SOCKET, SO_LINGER, pack("ll", 0, 0))} or dbg("setsockopt linger: $!"); + eval {setsockopt($conn->{sock}, IPPROTO_TCP, TCP_NODELAY, 1)} or eval {setsockopt($conn->{sock}, SOL_SOCKET, TCP_NODELAY, 1)} or dbg("setsockopt tcp_nodelay: $!"); + $conn->{sock}->autoflush(0); + + if (isdbg('sock')) { + my ($l, $t) = unpack "ll", getsockopt($conn->{sock}, SOL_SOCKET, SO_LINGER); + my $k = unpack 'l', getsockopt($conn->{sock}, SOL_SOCKET, SO_KEEPALIVE); + my $n = $main::is_win ? 0 : unpack "l", getsockopt($conn->{sock}, IPPROTO_TCP, TCP_NODELAY); + dbg("Linger is: $l $t, keepalive: $k, nagle: $n"); + } + } } -sub rcv_now { - my ($conn) = @_; - my ($msg, $err) = _rcv ($conn, 1); # 1 ==> rcv now - return wantarray ? ($msg, $err) : $msg; +sub dequeue +{ + my $conn = shift; + + if ($conn->{msg} =~ /\n/) { + my @lines = split /\r?\n/, $conn->{msg}; + if ($conn->{msg} =~ /\n$/) { + delete $conn->{msg}; + } else { + $conn->{msg} = pop @lines; + } + for (@lines) { + &{$conn->{rproc}}($conn, defined $_ ? $_ : ''); + } + } } sub _rcv { # Complement to _send - my ($conn, $rcv_now) = @_; # $rcv_now complement of $flush + my $conn = shift; # $rcv_now complement of $flush # Find out how much has already been received, if at all my ($msg, $offset, $bytes_to_read, $bytes_read); my $sock = $conn->{sock}; return unless defined($sock); - if (exists $conn->{msg}) { - $msg = $conn->{msg}; - $offset = length($msg) - 1; # sysread appends to it. - $bytes_to_read = $conn->{bytes_to_read}; - delete $conn->{'msg'}; # have made a copy - } else { - # The typical case ... - $msg = ""; # Otherwise -w complains - $offset = 0 ; - $bytes_to_read = 0 ; # Will get set soon - } - # We want to read the message length in blocking mode. Quite - # unlikely that we'll get blocked too long reading 4 bytes - if (!$bytes_to_read) { # Get new length - my $buf; - $conn->set_blocking(); - $bytes_read = sysread($sock, $buf, 4); - if ($! || ($bytes_read != 4)) { - goto FINISH; - } - $bytes_to_read = unpack ('N', $buf); - } - $conn->set_non_blocking() unless $rcv_now; - while ($bytes_to_read) { - $bytes_read = sysread ($sock, $msg, $bytes_to_read, $offset); - if (defined ($bytes_read)) { - if ($bytes_read == 0) { - last; - } - $bytes_to_read -= $bytes_read; - $offset += $bytes_read; - } else { - if (_err_will_block($!)) { - # Should come here only in non-blocking mode - $conn->{msg} = $msg; - $conn->{bytes_to_read} = $bytes_to_read; - return ; # .. _rcv will be called later - # when socket is readable again - } else { - last; - } - } - } - FINISH: - if (length($msg) == 0) { - $conn->disconnect(); + my @lines; +# if ($conn->{blocking}) { +# blocking($sock, 0); +# $conn->{blocking} = 0; +# } + $bytes_read = sysread ($sock, $msg, 1024, 0); + if (defined ($bytes_read)) { + if ($bytes_read > 0) { + $total_in += $bytes_read; + if (isdbg('raw')) { + my $call = $conn->{call} || 'none'; + dbgdump('raw', "$call read $bytes_read: ", $msg); + } + if ($conn->{echo}) { + my @ch = split //, $msg; + my $out; + for (@ch) { + if (/[\cH\x7f]/) { + $out .= "\cH \cH"; + $conn->{msg} =~ s/.$//; + } else { + $out .= $_; + $conn->{msg} .= $_; + } + } + if (defined $out) { + set_event_handler ($sock, write => sub{$conn->_send(0)}); + push @{$conn->{outqueue}}, $out; + } + } else { + $conn->{msg} .= $msg; + } + } + } else { + if (_err_will_block($!)) { + return ; + } else { + $bytes_read = 0; + } } - if ($rcv_now) { - return ($msg, $!); + +FINISH: + if (defined $bytes_read && $bytes_read == 0) { + &{$conn->{eproc}}($conn, $!) if exists $conn->{eproc}; + $conn->disconnect; } else { - &{$conn->{rcvd_notification_proc}}($conn, $msg, $!); - } + unless ($conn->{disable_read}) { + $conn->dequeue if exists $conn->{msg}; + } + } } -sub _new_client { - my $sock = $main_socket->accept(); - my $conn = bless { - 'sock' => $sock, - 'state' => 'connected' - }, $g_pkg; - my $rcvd_notification_proc = - &$g_login_proc ($conn, $sock->peerhost(), $sock->peerport()); - if ($rcvd_notification_proc) { - $conn->{rcvd_notification_proc} = $rcvd_notification_proc; - my $callback = sub {_rcv($conn,0)}; - set_event_handler ($sock, "read" => $callback); - } else { # Login failed - $conn->disconnect(); - } +sub new_client { + my $server_conn = shift; + my $sock = $server_conn->{sock}->accept(); + if ($sock) { + my $conn = $server_conn->new($server_conn->{rproc}); + $conn->{sock} = $sock; + blocking($sock, 0); + $conn->nolinger; + $conn->{blocking} = 0; + my ($rproc, $eproc) = &{$server_conn->{rproc}} ($conn, $conn->{peerhost} = $sock->peerhost(), $conn->{peerport} = $sock->peerport()); + $conn->{sort} = 'Incoming'; + if ($eproc) { + $conn->{eproc} = $eproc; + set_event_handler ($sock, error => $eproc); + } + if ($rproc) { + $conn->{rproc} = $rproc; + my $callback = sub {$conn->_rcv}; + set_event_handler ($sock, read => $callback); + } else { # Login failed + &{$conn->{eproc}}($conn, undef) if exists $conn->{eproc}; + $conn->disconnect(); + } + } else { + dbg("Msg: error on accept ($!)") if isdbg('err'); + } +} + +sub close_server +{ + my $conn = shift; + set_event_handler ($conn->{sock}, read => undef, write => undef, error => undef ); + $conn->{sock}->close; +} + +# close all clients (this is for forking really) +sub close_all_clients +{ + foreach my $conn (values %conns) { + $conn->disconnect; + } } +sub disable_read +{ + my $conn = shift; + set_event_handler ($conn->{sock}, read => undef); + return $_[0] ? $conn->{disable_read} = $_[0] : $_[0]; +} + +# #---------------------------------------------------- # Event loop routines used by both client and server @@ -304,28 +650,76 @@ sub set_event_handler { $rd_handles->remove($handle); } } + if (exists $args{'error'}) { + $callback = $args{'error'}; + if ($callback) { + $er_callbacks{$handle} = $callback; + $er_handles->add($handle); + } else { + delete $er_callbacks{$handle}; + $er_handles->remove($handle); + } + } } sub event_loop { - my ($pkg, $loop_count, $timeout) = @_; # event_loop(1) to process events once - my ($conn, $r, $w, $rset, $wset); + my ($pkg, $loop_count, $timeout, $wronly) = @_; # event_loop(1) to process events once + my ($conn, $r, $w, $e, $rset, $wset, $eset); while (1) { - # Quit the loop if no handles left to process - last unless ($rd_handles->count() || $wt_handles->count()); - ($rset, $wset) = - IO::Select->select ($rd_handles, $wt_handles, undef, $timeout); - foreach $r (@$rset) { - &{$rd_callbacks{$r}} ($r) if exists $rd_callbacks{$r}; - } - foreach $w (@$wset) { - &{$wt_callbacks{$w}}($w) if exists $wt_callbacks{$w}; - } + + # Quit the loop if no handles left to process + if ($wronly) { + last unless $wt_handles->count(); + + ($rset, $wset, $eset) = IO::Select->select(undef, $wt_handles, undef, $timeout); + + foreach $w (@$wset) { + &{$wt_callbacks{$w}}($w) if exists $wt_callbacks{$w}; + } + } else { + + last unless ($rd_handles->count() || $wt_handles->count()); + + ($rset, $wset, $eset) = IO::Select->select($rd_handles, $wt_handles, $er_handles, $timeout); + + foreach $e (@$eset) { + &{$er_callbacks{$e}}($e) if exists $er_callbacks{$e}; + } + foreach $r (@$rset) { + &{$rd_callbacks{$r}}($r) if exists $rd_callbacks{$r}; + } + foreach $w (@$wset) { + &{$wt_callbacks{$w}}($w) if exists $wt_callbacks{$w}; + } + } + + Timer::handler; + if (defined($loop_count)) { last unless --$loop_count; } } } +sub sleep +{ + my ($pkg, $interval) = @_; + my $now = time; + while (time - $now < $interval) { + $pkg->event_loop(10, 0.01); + } +} + +sub DESTROY +{ + my $conn = shift; + my $call = $conn->{call} || 'unallocated'; + my $host = $conn->{peerhost} || ''; + my $port = $conn->{peerport} || ''; + dbg("Connection $conn->{cnum} $call [$host $port] being destroyed") if isdbg('connll'); + $noconns--; +} + 1; __END__