package IO::SessionSet; # file: IO/SessionSet.pm # Figure 13.6: IO::SessionSet use strict; use Carp; use IO::Select; use IO::Handle; use IO::SessionData; use vars '$DEBUG'; $DEBUG = 0; # Class method new() # Create a new Session set. # If passed a listening socket, use that to # accept new IO::SessionData objects automatically. sub new { my $pack = shift; my $listen = shift; my $self = bless { sessions => {}, readers => IO::Select->new(), writers => IO::Select->new(), },$pack; # if initialized with an IO::Handle object (or subclass) # then we treat it as a listening socket. if ( defined($listen) and $listen->can('accept') ) { $self->{listen_socket} = $listen; $self->{readers}->add($listen); } return $self; } # Object method: sessions() # Return list of all the sessions currently in the set. sub sessions { return values %{shift->{sessions}} }; # Object method: add() # Add a handle to the session set. Will automatically # create a IO::SessionData wrapper around the handle. sub add { my $self = shift; my ($handle,$writeonly) = @_; warn "Adding a new session for $handle.\n" if $DEBUG; return $self->{sessions}{$handle} = $self->SessionDataClass->new($self,$handle,$writeonly); } # Object method: delete() # Remove a session from the session set. May pass either a handle or # a corresponding IO::SessionData wrapper. sub delete { my $self = shift; my $thing = shift; my $handle = $self->to_handle($thing); my $sess = $self->to_session($thing); warn "Deleting session $sess handle $handle.\n" if $DEBUG; delete $self->{sessions}{$handle}; $self->{readers}->remove($handle); $self->{writers}->remove($handle); } # Object method: to_handle() # Return a handle, given either a handle or a IO::SessionData object. sub to_handle { my $self = shift; my $thing = shift; return $thing->handle if $thing->isa('IO::SessionData'); return $thing if defined (fileno $thing); return; # undefined value } # Object method: to_session # Return a IO::SessionData object, given either a handle or the object itself. sub to_session { my $self = shift; my $thing = shift; return $thing if $thing->isa('IO::SessionData'); return $self->{sessions}{$thing} if defined (fileno $thing); return; # undefined value } # Object method: activate() # Called with parameters ($session,'read'|'write' [,$activate]) # If called without the $activate argument, will return true # if the indicated handle is on the read or write IO::Select set. # May use either a session object or a handle as first argument. sub activate { my $self = shift; my ($thing,$rw,$act) = @_; croak 'Usage $obj->activate($session,"read"|"write" [,$activate])' unless @_ >= 2; my $handle = $self->to_handle($thing); my $select = lc($rw) eq 'read' ? 'readers' : 'writers'; my $prior = defined $self->{$select}->exists($handle); if (defined $act && $act != $prior) { $self->{$select}->add($handle) if $act; $self->{$select}->remove($handle) unless $act; warn $act ? 'Activating' : 'Inactivating', " handle $handle for ", $rw eq 'read' ? 'reading':'writing',".\n" if $DEBUG; } return $prior; } # Object method: wait() # Wait for I/O. Handles writes automatically. Returns a list of IO::SessionData # objects ready for reading. # If there is a listen socket, then will automatically do an accept() and return # a new IO::SessionData object for that. sub wait { my $self = shift; my $timeout = shift; # Call select() to get the list of sessions that are ready for reading/writing. croak "IO::Select->select() returned error: $!" unless my ($read,$write) = IO::Select->select($self->{readers},$self->{writers},undef,$timeout); # handle queued writes automatically foreach (@$write) { my $session = $self->to_session($_); warn "Writing pending data (",$session->pending+0," bytes) for $_.\n" if $DEBUG; my $rc = $session->write; } # Return list of sessions that are ready for reading. # If one of the ready handles is the listen socket, then # create a new session. # Otherwise return the ready handles as a list of IO::SessionData objects. my @sessions; foreach (@$read) { if ($_ eq $self->{listen_socket}) { my $newhandle = $_->accept; warn "Accepting a new handle $newhandle.\n" if $DEBUG; my $newsess = $self->add($newhandle) if $newhandle; push @sessions,$newsess; } else { push @sessions,$self->to_session($_); } } return @sessions; } # Class method: SessionDataClass # Return the string containing the name of the session data # wrapper class. Subclass and override to use a different # session data class. sub SessionDataClass { return 'IO::SessionData'; } 1;