NAME

POE::Tutorial - A beginers guide to POE


SYNOPSIS


DESCRIPTION

Using POE for the first time can be daunting. POE is a large collection of complex modules. POE is also a completely different way of thinking about solving a problem.

This document is an attempt to help beginers get quickly up to speed with POE. It is roughly structured in 3 sections: understanding the event-oriented programming, an overview of POE and details of specific POE modules.


EVENT-ORIENTED PROGRAMMING

Events

POE uses an event loop to implement cooperative multi-tasking. To understand what an event loop is, we need to know what an event is:

An event is something that happened and the details of what happened.

Something happened is purposefully very vague. It could be data is now available over the network, an output buffer has been flushed to the serial port, the user has interacted with an applications user interface, a lapse of time is over. It could also be a more complex event: a new HTTP request has arrived, a new user has joined an IRC client, one part of the application wishes to notify another part of an application.

Details of what happened is also vague: it really depends on the type of event:

Data over the network

The details could be the data itself, the socket that the data arrived on, and so on.

User interaction

The details could be the UI element in question, the type of interaction (mouse click, key press, etc) and so on

Timer

The details could be some arbitrary data associated with the timer

New HTTP request

The details could be the socket, a the HTTP::Request manpage object, and so on.

Intra-application notification

The details could be a new database record, the contents of a user session.

Event handlers

Once an event has been noticed, POE will route the event to one or more destinations, known as event handlers. (For historical reasons event handlers are also called states and state handlers in POE.) An event handler is just some Perl code that has been associated with the event. Its job is to react to the event:

Data over the network

Log the data to a database.

User interaction

Update some element in the UI.

Timer

Rotate a log file.

HTTP request

Calculate a fibonacci sequence and send this back to the browser.

Intra-application notification

Write current status of an operation to a check-point file.

Event sources

Events could come from outside the application (network, filesystem, OS signals) or from within the application.

the POE::Kernel manpage deals with many external event sources. Others are detected by themultitude of POE::Wheel modules. Wheels can also do some processing on the data with POE::Filters. At higher level of abstraction, the POE::Session manpages and the POE::Component manpages can detect or create events. But this is getting a bit ahead of it all.

Posting an event

Once an event is detected, a message is given to the POE kernel to be routed to an event handler. This is called posting an event. Confusingly, both the something that happened and the message that gets posted are both called events.


OVERVIEW OF POE

The above is pretty vague: an event is detected, a message (aka event) is posted, the POE kernel routes it to an event handler that reacts accordingly. POE applications spend their entier time doing this: event->reaction, event->reaction, event->reaction. Sort of like going around in circles; it's an event loop.

This is important to remember. Important enough that I'm going to include an ugly diagram to draw attention to it

    /---> detect event
    |        |
    |       \|/
    |     post event
    |        |
    |       \|/
    |     route event
    |        |
    |       \|/
    |     handle event
    |        |
    \________/

POE::Kernel

The POE kernel is the master of ceremoies in a POE application; it handles interaction with the outside world, it routes events to their handlers. It also makes sure that something is always happening. If nothing is happening, it checks to see if something could happen. If nothing could happen ever again, then obiously the application has finished its task and the kernel exits.

Queue

There is only one POE kernel within an application. It is available as the $poe_kernel object, exported by POE.

    use POE;
    $poe_kernel->run();

Most action within a POE application is mediated by the kernel. Most interaction with other POE components happens via the kernel.

POE::Wheel

While the POE kernel can detect that something has happened (for instance: network data is availabe), most of the time you will want a higher level of abstraction (for instance: reading the data). But why reinvent the wheel? Code for reading files, sockets, handling sub-processes and more already exists in the form of the the POE::Wheel manpage library of modules.

You can also think of these modules as the wheels that POE runs on; where the rubber hits the pavement, as it were.

As an example, take the POE::Wheel::ReadWrite manpage. It can read from a socket or file handle, write to a socket or file handle. It can also detect errors. All these things are then followed by an event being posted.

Or the POE::Wheel::ReadLine manpage, a wrapper around the Term::ReadLine manpage.

POE::Filter

What happens if the data that Wheel is incomplete? Maybe the data being sent was bigger then a network packet and is arriving in pieces. Your application is only really interested in the complete data, not the individual pieces as they arrive. The the POE::Filter manpage collection of modules will handle each piece until it has everything nessary. Only then will it tell the wheel to send a message.

Filters work 2 ways; raw data from the ``outside'' is split into messages, and messages from the application are combined into raw data when sent to the ``outside''

Examples:

POE::Filter::Line

Waits until it sees an end-of-line condition (configurable), then post the entire line as an event. If sending data, makes sure it is end-of-line terminated. See the POE::Fitler::Line manpage for more details.

POE::Filter::Block

Seperates data into blocks of X bytes, then posts those bytes as an event. Can also handle variable length data structures. Variable-length blocks are also possible. See the POE::Filter::Block manpage for more details.

POE::Filter::Reference

When sending, complex Perl structures are serialised before being sent, for example with Storable/nfreeze. When receiving, the raw data is converted back to a Perl structure, for example with Storable/thaw.

Because the first populare generalised serialisation module was called FreezeThaw, these operations are reffered to as freezing and thawing.

POE::Session

All event handlers in a POE application are collected into the POE::Session manpages. Sessions could be thought of as threads or processes, but remember that POE is cooperative multitasking; only one event handler in one session can be running at any given time. If that event handler never exits, no other event handler can be run.

A session maintains the list of event handlers. When the POE kernel routes an event to its handler, it is the session that calls the handler's code.

POE::Component

The modules in the POE::Component can be pretty much anything. Like DBIx, it is where code that uses POE or extends POE lives. There are a few conventions on how a component should behave, but there are no hard rules. In general, they are implemented as POE::Sessions, and started with either ->spawn or ->create.

Example components are the popular POE::Component::IRC, an IRC client, POE::Component::Server::SimpleHTTP, a web server and POE::Component::DBIAgent for interacting with a database.


DETAILS OF POE

Event handlers

Event handlers are created by POE::Session in 3 flavours: inline, package and object. All 3 can be used in the same session. Multiple objects or packages can be used by the same session:

    POE::Session->create(
            inline_states => {
                    _stop => sub { warn "Shutdown" },
                    ctrl_C => \&ctrl_C
                },
            object_states => [
                    $obj1 => [ '_start' ],
                    $obj2 => { load => 'file_load', save => 'file_save' }
                ],
            package_states => [
                    My::Package => [ '_child', '_default' ]
                ],
        );

This sets up 7 event handlers:

_stop

An anonymous coderef that is called when the POE kernel believes the session has nothing to do.

ctrl_C

A subroutine that is called when the ctrl_C event is posted.

_start

When the session is first created, the _start method of $obj1 will be called.

load, save

These event handlers are mapped to object methods of $obj2 that have a different name. The load event will call $obj2->file_load and the save event, $obj2->file_save.

_child

This event handler is called when this session gains or looses a child session. Discussion of child sessions is beyond the scope of this document.

_default

This event handler is called when there is no other event handler for an event. You might be thinking ``I know, I'll install a _default handler. If I have all my event handlers installed already set up, I'll be able to catch typos in mistyped event names.'' The problem with this is that there is a back-ground noise of events that POE kernel posts, dealing with various house-keeping issues.

It is also possible to add and remove event handlers during application execution:

    $poe_kernel->state( $new_event => $object => $method );
    $poe_kernel->state( $old_event );

This adds a new event handler for $new_event, which will call $method of $object. It also removes the event handler for $old_event.

It is worth pointing out that a event's name is not limited to Perl identifiers. Then can contain spaces, punctuation, UTF8, anything. This is why you can map event handlers to methods of a different name.

When the POE kernel decides an event must the POE::Session manpage will call your handler with the arguement list (@_) populated with all the details of an event. Individual details are fetched via constant offsets into this array. So your code will start to look like this:

    sub my_handler {
        my( $session, $sender, $caller_file ) = 
                @_[ SESSION, SENDER, CALLER_FILE ];
        my( $id, $data ) = @_[ ARG0, ARG1 ];
        # ....
    }

SESSION is the current session, SENDER is the session that posted an event (which could also be the POE kernel). CALLER_FILE is the source code file the event was posted from. There are others, consult the POE::Session manpage.

ARG0 and ARG1 are details specific to this event. If an event can be thought of as a subroutine (which it shouldn't), ARG0, ARG1 are the subroutine's parameters.

If like me you find this to be cumbersome there are other ways of managing those details. See the POE::Sugar::Args manpage and the POE::Session::PlainCall manpage.

Application events

If your application wishes to post an event, it does so via the POE kernel:

    $poe_kernel->post( $session1 => $event1, @args );
    $poe_kernel->yield( $event2, @args );
    my @B = $poe_kernel->call( $session3, $event3, @args );

The first line will post an event called $event1 to the session reffered to in $session1. The event's handler will not be called immediatly; the event is placed on an event queue by the POE kernel and will called after the current event handler exits. This is called an asynchronous event.

The second line will post an event called $event2 to the currently active session. This is also asynchronous event. yield can be thought of as syntatic sugar for:

    $poe_kernel->post( $_[SESSION], $event );

The second line will call the envent handler for $event3 in $session3 right away, and the event handler's return will be placed into @B. This is a synchronous event. They should be used sparingly, because they break POE's cooperative nature.

In all 3 lines passed @args to the POE kernel. These are the details of a given event. They will be available to the event handler as $_[ARG0], $_[ARG1]...

Sockets

Most of the time you will be using POE::Wheels to create and maintain sockets.

Signals

There are 2 kinds of signals: external, OS signals, and internal ``fake'' signals.

External signals are detected by the kernel, and routed sessions that have registered an interest in them.

    $poe_kernel->sig( INT => 'ctrl_C' );

Some signals are terminal; if your application doesn't handle them and tell the kernel to continue, your application will exit. See Signals in the POE::Kernel manpage.

The kernel also uses signals to send details of the life of the application.

You can also use signals to multicast a message to all interested sessions:

    $poe_kernel->signal( $poe_kernel, 'my-signal', @details );

To catch that signal, you would use

    $poe_kernel->sig( 'my-signal' => 'sig_handler' );

Timers

Aliases

There are 3 ways to address a session: with a session object, with a session ID, or with a session alias.

Session object

create in the POE::Session manpage returns a session object. You can use this object post events to the session. However, you would be ill advised to keep a reference to a session object; this prevents POE's garbage collection from doing its job.

Sesssion ID

Each session has a unique ID. This ID is avaible via ID in the POE::Session manpage and is guaranteed unique within a given POE kernel's lifetime. And instead of keeping session object, you can use it's ID.

    my $id = POE::Session->create();
    # ...
    $poe_kernel->post( $id => 'event' );

Session aliases

Using session IDs requires that you tell all sessions the IDs of all the sessions they wish to interact with. This can be cumbersom and can lead to chicken-and-egg problems.

Sessions can also be given one or more aliases: strings that will also uniquely identify the session.

    $poe_kernel->alias_set( 'honk' );
    # ...
    $poe_kernel->post( honk => 'event' );

What's more, a simple number is not very informative, however. Text like ``posting to session 3'' in debug output doesn't tell much about session 3, compared to ``posting to session 'honk'''

Resources

Shutdown

When will an application exit? is a common question. The answer is when there are no more sessions.

When does session stop, then?. A session will be garbage collected when the POE kernel notices that session is not doing anything and that it can't do anything in the future.

Not doing anything means that it has no events in the queue, that it hasn't sent an event that is still in the queue, no timers set.

Can't do anything means that it doesn't have any files or sockets open (future external events), and that it doesn't have an alias set (future internal events).

So, to get a session to go away, you close all its sockets (generaly by getting rid of its wheels), remove all pending timers, and remove its alias.

What's more, if all the remaining sessions are kept around solely because they have aliases, the POE kernel will deem the sessions as having finished their task and will exit.


A LARGE EXAMPLE

Here, in less then 100 lines of code, is a primitive pure-perl chat server.

    use POE;
    Chatter->spawn( port => 4321 );
    $poe_kernel->run;
    package Chatter;
    use strict;
    use warnings;
    use POE;
    use POE::Component::Server::TCP;
    sub spawn {
        my( $package, @args ) = @_;
        my $self = bless {@args}, $package;
        return POE::Session->create(
                            object_states => [
                                $self => [ qw( _start connect disconnect
                                               error said sig_INT ) ]
                            ] );
    }

The spawn class method is very simple: we create an object, then create a session that uses object methods as event handlers. One common gotcha is to create a new method but forget to add it to the object_states list. the POE::Declare manpage can help you avoid this.

While create is running, the _start event is posted. This means that a POE event handler is running even before $poe_kernel->run was called.

    sub _start {
        my( $self ) = @_;
        $self->{alias} = "Chat server";
        $poe_kernel->alias_set( $self->{alias} );
        $self->{server} = POE::Wheel::SocketFactory->new(
                        BindPort => $self->{port}
                        SuccessEvent => 'connect',
                        FailureEvent => 'error',
                    );
        $poe_kernel->sig( INT => 'sig_INT' );
    }

Again, very simple to follow: create an alias for the session, create a wheel that listens on the given port, and look for SIGINT (aka Control-C).

    sub connect {
        my( $self, $socket ) = @_[ OBJECT, ARG0 ];
        my $wheel = POE::Wheel::ReadWrite->new(
                        Handle => $socket,
                        InputEvent  => 'said',
                        ErrorEvent  => 'disconnect'
                    );
        $wheel->put( "Greetings" );
        $self->{clients}{ $wheel->ID } = $wheel;
    }

When a client connects to the server, we receive a connect event from the wheel. We create another wheel to handle interaction with the new client, send it a greeting banner and keep ahold of the wheel.

    sub error {
        my ($operation, $errnum, $errstr) = @_[ OBJECT, ARG0, ARG1, ARG2];
        warn "Server $operation error $errnum: $errstr\n";
        delete $_[HEAP]{server};
    }

If the server wheel encoutered an error, for example the port was already in use, this event handler is called.

    sub disconnect {
        my( $self, $op, $errnum, $errstr, $wid ) = 
                @_[ OBJECT, ARG0, ARG1, ARG2, ARG3 ];
        delete $self->{clients}{ $wid };
        if( $op eq 'read' and $errnum == 0 ) {
            $poe_kernel->yield( said => "*CARRIER LOST*", $wid );
        }
        return;
    }

If a client wheel encounters an error the disconnect event is posted from the wheel. We react by getting rid of the wheel. $op eq 'read' and $errnum == 0 is how we detect a normal disconnect event. We only send a message to other clients on normal disconnect. Otherwise we risk sending messages to client wheels that are in the process of shuting down, which would provoke more error events, which in turn would post more messages.

    sub said {
        my( $self, $input, $wid ) = @_[ OBJECT, ARG0, ARG1 ];
        foreach my $id ( keys %{ $self->{clients} } ) {
            next if $id == $wid;
            $self->{clients}{ $id }->put( "$wid> $input" );
        }
    }

When a user types something into their client, the client wheel filter out the end-of-line, then posts the said event. We send the message to all the other client wheels.

    sub sig_INT {
        my( $self ) = @_[ OBJECT ];
        delete $self->{clients};
        delete $self->{server};
        $poe_kernel->alias_set( delete $self->{alias} ) if $self->{alias};
    }


BEST PRACTICES


AUTHORS

Philip Gwyn


SEE ALSO