On May 19, 2011, at 03:18, Gabor Szabo wrote:

> On Thu, May 19, 2011 at 5:28 AM, Rocco Caputo <rcap...@pobox.com> wrote:
>> On May 18, 2011, at 09:30, Gabor Szabo wrote:
>> 
>>> I have tried to setup a small application that would listen on a TCP
>>> port and get commands 'start' and 'stop',
>>> When receiving start it should create a new POE::Session to count.
>>> When receiving stop it should stop the counting and destroy that
>>> POE::Session. It seems I managed to do it but I'd be glad to receive
>>> comments about the code:
>> 
>> [code removed]
>> 
>> Hi, Gabor.  Please see the revised code below.  The major change was to 
>> eliminate the counting session.  They didn't seem necessary, and their 
>> presence just complicated things.
> 
> thanks for your reply. That looks indeed more simple. Reading again
> the documentation,
> do I understand correctly that already the ClientConnected and
> ClientInput are callbacks of
> the individual, connection related POE::Session?

Yes, all the /^Client/ callbacks are for individual connection related 
POE::Session instances.

> Further working on the application I am writing, I'll need to have
> several counters
> and keep the counters work even after the original requester disconnected.
> Trying with one, in my example it worked as the counter Session was not 
> related
> to the connection.
> I wonder if in your solution, can I tell the session to remain active
> even after
> the user disconnected? Can I also remove it from the concurrency counter?
> 
> Would in this case the separate POE::Session work better?

Yes.  I assume your independent counters have names.  I've attached a revised 
design to account for multiple named timers.  Since the code's getting larger, 
I modularized it a bit, making it even larger still.  Sample output:

1) macbookpoe:~% nc localhost 12345
Type 'start', 'stop', or 'list':
list
No known counters.
start aaa
Starting counter aaa...
start bbb
Starting counter bbb...
start ccc
Starting counter ccc...
list
Known counters at Sun May 22 01:56:51 2011: aaa:30152, bbb:13406, ccc:3525
stop bbb
Stopping counter bbb...
list
Known counters at Sun May 22 01:56:54 2011: aaa:50326, ccc:23699
^C
1) macbookpoe:~% nc localhost 12345
Type 'start', 'stop', or 'list':
list
Known counters at Sun May 22 01:56:57 2011: aaa:70739, ccc:44112
^C

-- 
Rocco Caputo <rcap...@pobox.com>

#!/usr/bin/perl

use warnings;
use strict;
use 5.010;

use POE qw(Component::Server::TCP);

{
  package Counter;

  use warnings;
  use strict;
  use POE;  # For KERNEL, HEAP, ARG0, etc.

  my %counter;

  # Spawn the Counter session.

  # There may be only one due to the scoping of %counter.
  #
  # A design incorporating multiple Counter sessions would imply each
  # had its own counter namespace.  I'd use $_[HEAP]{counter} in that
  # case.
  #
  # But for now I'm using %counter in closures to simplify the code.

  sub spawn {
    POE::Session->create(
      inline_states => {
        _start => sub {
          # The alias is sufficient to keep this session
          # around as long as other sessions exist.
          $_[KERNEL]->alias_set("counter");
        },
        count => sub {
          my ($kernel, $counter_name) = @_[KERNEL, ARG0];

          # The counter was deleted in stop_counter?
          # By returning here, we stop perpetuating the recursive
          # yield() that drives this counter.

          return unless exists $counter{$counter_name};

          print "Counter $counter_name = ", ++$counter{$counter_name}, "\n";

          # The yield() here perpetuates the counter.
          $kernel->yield( count => $counter_name );
        },
        start_counter => sub {
          my ($kernel, $counter_name) = @_[KERNEL, ARG0];

          # Don't start a counter that's already running.
          return if exists $counter{$counter_name};

          $counter{$counter_name} = 0;
          $kernel->yield( count => $counter_name );
        },
        stop_counter => sub {
          my ($kernel, $counter_name) = @_[KERNEL, ARG0];

          # Don't stop a counter that's not running.
          return unless exists $counter{$counter_name};

          # Deleting the counter stops it.
          delete $counter{$counter_name};
        },
      },
    );
  }

  # These subs that follow are plain class methods.
  #
  # I've put the POE callbacks into anonymous coderefs above to keep
  # them visually distinct from plain methods.  I often also use
  # prefixes like _poe_foo() to indicate private POE callbacks.

  sub start {
    my ($class, $counter_name) = @_;

    # start() passes a message from the client connection session to
    # the counter session.  The counters run in a dedicated session
    # separate from each connection, so connections can come and go
    # independently from counters.

    $poe_kernel->post( counter => start_counter => $counter_name );
  }

  sub stop {
    my ($class, $counter_name) = @_;
    delete $counter{$counter_name};
  }

  sub list {
    return %counter;
  }
}

### Abstract the server into its own package for tidiness.

{
  package Server;

  use warnings;
  use strict;
  use POE;  # For KERNEL, HEAP, ARG0, etc.

  sub spawn {
    my ($class, $port) = @_;

    POE::Component::Server::TCP->new(
      Port            => $port,
      ClientInput     => \&_poe_client_input,
      ClientConnected => \&_poe_client_connected,
    );

    print "Counter server is listening on *:$port...\n";
  }

  sub _poe_client_input {
    my ($kernel, $heap, $input) = @_[KERNEL, HEAP, ARG0];

    print "client input: $input\n";

    if ($input =~ /^start\s+(\S+)/) {
      $heap->{client}->put("Starting counter $1...");
      Counter->start($1);
      return;
    }

    if ($input =~ /^stop\s+(\S+)/) {
      $heap->{client}->put("Stopping counter $1...");
      Counter->stop($1);
      return;
    }

    if ($input eq 'list') {
      my %counters = Counter->list();
      my @counter_names = sort keys %counters;

      if (scalar keys %counters) {
        $heap->{client}->put(
          "Known counters at " . scalar(localtime) . ": " .
          join(", ", map { "$_:$counters{$_}" } sort keys %counters)
        );
      }
      else {
        $heap->{client}->put("No known counters.");
      }

      return;
    }

    $heap->{client}->put("Invalid command '$input'");
    return;
  }

  sub _poe_client_connected {
    $_[HEAP]{client}->put("Type 'start', 'stop', or 'list':");
  }
}

### Main program.
#
# The TCP server could also be abstracted into its own package.

Counter->spawn();
Server->spawn(12345);

POE::Kernel->run();
exit;

Reply via email to