Sign In/My Account | View Cart  
advertisement


Listen Print

Building Applications with POE
by Matt Cashner | Pages: 1, 2

Step 2: A Wheel

Chances are that our users want to send data in one of the standard UNIX methods -- sockets, pipes, and so on. Lucky for us, POE already has Wheels to deal with just about any methodology of data transfer you can imagine. Let's work with a method that should work just about anywhere, TCP sockets. POE::Wheel::SocketFactory provides the functionality we need. First, we need a session to plug the wheel into. (Remember that wheels mutate sessions to provide new functionality.)

  POE::Session->create(
    inline_states => {
      _start      => \&start,
      factory_success => \&factory_success,
      
      client_input  => \&client_input,
      client_error  => \&client_error,

      fatal_error   => sub { die "A fatal error occurred" },
      _stop       => sub {},
    },
  );

  POE::Kernel->run();

This session will be our controller for the wheels we need to perform socket operations. Each wheel-based event provides a unique identifier so it is possible to handle more than one client per session.

When the session starts up, we spin up the SocketFactory wheel. With the Reuse flag on, SocketFactory will continuously listen on the specified port and address, handing us events for each client. The unique id passed to the SuccessEvent identifies each client.

  sub start {
    $_[HEAP]->{factory} = POE::Wheel::SocketFactory->new(
      BindAddress   => '127.0.0.1',
      BindPort    => '31337',
      SuccessEvent  => 'factory_success',
      FailureEvent  => 'fatal_error',
      SocketProtocol  => 'tcp',
      Reuse       => 'on',
    );
  }

When a client makes a connection, the SocketFactory lets us know. It is our job to figure out what to do with the filehandle SocketFactory built for us. In this case, we want read/write functionality using the filter we built above. POE::Wheel::ReadWrite provides this functionality, including the ability to plug in our filter.

  sub factory_success {
    my( $handle, $wheel_id ) = @_[ARG0, ARG1];
    $_[HEAP]->{clients}->{ $wheel_id }  =
      POE::Wheel::ReadWrite->new(
        Handle    => $handle,
        Driver    => POE::Driver::SysRW->new(),
        Filter    => POE::Filter::SimpleQueryString->new(),
        InputEvent  => 'client_input',
      );
  }

Now the data path is set up. We have the ability for programs to connect to a port and provide data in our simple format. What to do with the data though? Let's simply print it out and echo it back to the client.

  sub client_input {
    my ($input, $wheel_id) = @_[ARG0, ARG1];

    use Data::Dumper;
    print Dumper $input;

    $_[HEAP]->{clients}->{ $wheel_id }->put( $input );
  }

Data::Dumper handles printing out the structure for us. The put() call puts the structure back out onto the wire. If our algorithms are correct, we should get the same data back that we put in.

  sungo@cthulu% telnet localhost 31337
  Trying 127.0.0.1...
  Connected to localhost.
  Escape character is '^]'.
  foo=bar

The server prints out:

  sungo@cthulu% perl -Ilib examples/server.pl
  $VAR1 = { 
    'foo' => 'bar'
  };

And then echoes back to us:

  foo=bar

We're in business.

Step 3: A Component

Man, that was a lot of code to get a simple TCP server up and running. Surely this can be simplified. Again, POE itself comes to the rescue. POE ships with a component specifically designed to simplify TCP server creation. We can replace all that code above with a simple call to the component's constructor.

  POE::Component::Server::TCP->new(
    Address => '127.0.0.1',
    Port  => '31337',

    ClientFilter => "POE::Filter::SimpleQueryString",
    ClientInput => sub {
      my $input = $_[ARG0];
      use Data::Dumper;
      print Dumper $input;

      $_[HEAP]->{client}->put($input);
    }

And we're done. The downside is that Server::TCP doesn't allow for argument passing to the filter's constructor and we lose the flexibility of doing things by hand. For a lot of problems, however, this component does the trick quite nicely.

We can make this even easier for our users by making our own component. For the purpose of this example, we're going to wrap the smaller code above instead of the larger wheel based example. There is no reason why you couldn't use the wheel-based code in your component, however.

  package POE::Component::SimpleQueryString;

  use warnings;
  use strict;

  use POE;
  use POE::Component::Server::TCP;

  use POE::Filter::SimpleQueryString;

  use Carp qw(croak carp);

  sub new {
    my $class = shift;
    my %args = @_;

    my $addr = delete $args{ListenAddr} || croak "ListenAddr required";
    my $port = delete $args{ListenPort} || croak "ListenPort required";
    my $input_event = delete $args{InputEvent} || 
      croak "InputEvent required";

    my $server = POE::Component::Server::TCP->new(
            Address => $addr,
            Port => $port,

            ClientInput => $input_event,
            ClientFilter => "POE::Filter::SimpleQueryString",
           );
    
    return $server;
  }

  1;

Now our users can just load up the component like so:

  POE::Component::SimpleQueryString->new(
    ListenAddr => '127.0.0.1',
    ListenPort => '31337',
    InputEvent => sub {
      my $input = $_[ARG0];
      use Data::Dumper;
      print Dumper $input;

      $_[HEAP]->{client}->put($input);       
    },
  );

Conclusion

We have seen how to build POE filters and components and combine them with wheels and custom code to create flexible and maintainable programs. The code examples provided above may be downloaded under the BSD License.