use IO::Multiplex; my $mux = new IO::Multiplex; $mux->add($fh1); $mux->add(\*FH2); $mux->set_callback_object(...); $mux->listen($server_socket); $mux->loop; sub mux_input { ... }
"IO::Multiplex" is designed to take the effort out of managing multiple file handles. It is essentially a really fancy front end to the "select" system call. In addition to maintaining the "select" loop, it buffers all input and output to/from the file handles. It can also accept incoming connections on one or more listen sockets.
You may have one callback object registered for each file handle, or one global one. Possibly both --- the per-file handle callback object will be used instead of the global one.
Each file handle may also have a timer associated with it. A callback function is called when the timer expires.
The method should remove the data that it has consumed from the reference supplied. It may leave unconsumed data in the input buffer.
The basic method for handing output to the multiplexer is the "write" method, which simply takes a file descriptor and the data to be written, like this:
$mux->write($fh, "Some data");
For convenience, when the file handle is "add"ed to the multiplexer, it is tied to a special class which intercepts all attempts to write to the file handle. Thus, you can use print and printf to send output to the handle in a normal manner:
printf $fh "%s%d%X", $foo, $bar, $baz
Unfortunately, Perl support for tied file handles is incomplete, and functions such as "send" cannot be supported.
Also, file handle object methods such as the "send" method of "IO::Socket" cannot be intercepted.
use IO::Socket; use IO::Multiplex; # Create a multiplex object my $mux = new IO::Multiplex; # Connect to the host/port specified on the command line, # or localhost:23 my $sock = new IO::Socket::INET(Proto => 'tcp', PeerAddr => shift || 'localhost', PeerPort => shift || 23) or die "socket: $@"; # add the relevant file handles to the mux $mux->add($sock); $mux->add(\*STDIN); # We want to buffer output to the terminal. This prevents the program # from blocking if the user hits CTRL-S for example. $mux->add(\*STDOUT); # We're not object oriented, so just request callbacks to the # current package $mux->set_callback_object(__PACKAGE__); # Enter the main mux loop. $mux->loop; # mux_input is called when input is available on one of # the descriptors. sub mux_input { my $package = shift; my $mux = shift; my $fh = shift; my $input = shift; # Figure out whence the input came, and send it on to the # other place. if ($fh == $sock) { print STDOUT $$input; } else { print $sock $$input; } # Remove the input from the input buffer. $$input = ''; } # This gets called if the other end closes the connection. sub mux_close { print STDERR "Connection Closed\n"; exit; }
This example is a simple chat server.
use IO::Socket; use IO::Multiplex; my $mux = new IO::Multiplex; # Create a listening socket my $sock = new IO::Socket::INET(Proto => 'tcp', LocalPort => shift || 2300, Listen => 4) or die "socket: $@"; # We use the listen method instead of the add method. $mux->listen($sock); $mux->set_callback_object(__PACKAGE__); $mux->loop; sub mux_input { my $package = shift; my $mux = shift; my $fh = shift; my $input = shift; # The handles method returns a list of references to handles which # we have registered, except for listen sockets. foreach $c ($mux->handles) { print $c $$input; } $$input = ''; }
# Paste the above example in here, up to but not including the # mux_input subroutine. # mux_connection is called when a new connection is accepted. sub mux_connection { my $package = shift; my $mux = shift; my $fh = shift; # Construct a new player object Player->new($mux, $fh); } package Player; my %players = (); sub new { my $package = shift; my $self = bless { mux => shift, fh => shift } => $package; # Register the new player object as the callback specifically for # this file handle. $self->{mux}->set_callback_object($self, $self->{fh}); print $self->{fh} "Greetings, Professor. Would you like to play a game?\n"; # Register this player object in the main list of players $players{$self} = $self; $mux->set_timeout($self->{fh}, 1); } sub players { return values %players; } sub mux_input { my $self = shift; shift; shift; # These two args are boring my $input = shift; # Scalar reference to the input # Process each line in the input, leaving partial lines # in the input buffer while ($$input =~ s/^(.*?)\n//) { $self->process_command($1); } } sub mux_close { my $self = shift; # Player disconnected; # [Notify other players or something...] delete $players{$self}; } # This gets called every second to update player info, etc... sub mux_timeout { my $self = shift; my $mux = shift; $self->heartbeat; $mux->set_timeout($self->{fh}, 1); }
$mux = new IO::Multiplex;
$socket = new IO::Socket::INET(Listen => ..., LocalAddr => ...); $mux->listen($socket);
Connections will be automatically accepted and "add"ed to the multiplex object. "The mux_connection" callback method will also be called.
$mux->add($fh);
As a side effect, this sets non-blocking mode on the handle, and disables STDIO buffering. It also ties it to intercept output to the handle.
$mux->remove($fh);
If a file handle is supplied, the callback object is specific for that handle:
$mux->set_callback_object($object, $fh);
Otherwise, it is considered a default callback object, and is used when events occur on a file handle that does not have its own callback object.
$mux->set_callback_object(__PACKAGE__);
The previously registered object (if any) is returned.
See also the CALLBACK INTERFACE section.
$mux->kill_output($fh);
$output = $mux->outbuffer($fh); $mux->outbuffer($fh, $output);
$input = $mux->inbuffer($fh); $mux->inbuffer($fh, $input);
If the "Time::HiRes" module is installed, the timers may be specified in fractions of a second.
Timers are not reset automatically.
$mux->set_timeout($fh, 23.6);
Use "$mux->set_timeout($fh, undef)" to cancel a timer.
@handles = $mux->handles;
$mux->loop;
$mux->endloop;
$saddr = $mux->udp_peer($fh);
$is_udp = $mux->is_udp($fh);
$mux->write($fh, "'ere I am, JH!\n");
If the shutdown is for reading, it happens immediately. However, shutdowns for writing are delayed until any pending output has been successfully written to the socket.
$mux->shutdown($socket, 1);
$mux->close($fh);
All methods receive a reference to the callback object (or package) as their first argument, in the traditional object oriented way. References to the "IO::Multiplex" object and the relevant file handle are also provided. This will be assumed in the method descriptions.
sub mux_input { my $self = shift; my $mux = shift; my $fh = shift; my $data = shift; # Process each line in the input, leaving partial lines # in the input buffer while ($$data =~ s/^(.*?\n)//) { $self->process_command($1); } }
In this example, we send a final reply to the other end of the socket, and then shut it down for writing. Since it is also shut down for reading (implicly by the EOF condition), it will be closed once the output has been sent, after which the mux_close callback will be called.
sub mux_eof { my $self = shift; my $mux = shift; my $fh = shift; print $fh "Well, goodbye then!\n"; $mux->shutdown($fh, 1); }
Copyright 2001-2008 Rob Brown <bbb@cpan.org>
Released under the same terms as Perl itself.
$Id: Multiplex.pm,v 1.45 2015/04/09 21:27:54 rob Exp $