=head1 NAME POE::Tutorial - A beginers guide to POE =head1 SYNOPSIS =head1 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. =head1 EVENT-ORIENTED PROGRAMMING =head2 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: B I 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. I
is also vague: it really depends on the type of event: =over 4 =item Data over the network The details could be the data itself, the socket that the data arrived on, and so on. =item User interaction The details could be the UI element in question, the type of interaction (mouse click, key press, etc) and so on =item Timer The details could be some arbitrary data associated with the timer =item New HTTP request The details could be the socket, a L object, and so on. =item Intra-application notification The details could be a new database record, the contents of a user session. =back =head2 Event handlers Once an event has been noticed, POE will route the event to one or more destinations, known as I. (For historical reasons event handlers are also called I and I 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: =over 4 =item Data over the network Log the data to a database. =item User interaction Update some element in the UI. =item Timer Rotate a log file. =item HTTP request Calculate a fibonacci sequence and send this back to the browser. =item Intra-application notification Write current status of an operation to a check-point file. =back =head2 Event sources Events could come from outside the application (network, filesystem, OS signals) or from within the application. L deals with many external event sources. Others are detected by themultitude of L modules. Wheels can also do some processing on the data with Ls. At higher level of abstraction, Ls and Ls can detect or create events. But this is getting a bit ahead of it all. =head2 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 I an event. Confusingly, both the I and the I that gets posted are both called Is. =head1 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 L. 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 | | \________/ =head2 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 I 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 C<$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. =head2 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 L 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 L. 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 L, a wrapper around L. =head2 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 L 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: =over 4 =item 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 L for more details. =item 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 L for more details. =item POE::Filter::Reference When sending, complex Perl structures are serialised before being sent, for example with L. When receiving, the raw data is converted back to a Perl structure, for example with L. Because the first populare generalised serialisation module was called L, these operations are reffered to as I and I. =back =head2 POE::Session All event handlers in a POE application are collected into Ls. 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. =head2 POE::Component The modules in the POE::Component can be pretty much anything. Like L, 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 C<-Espawn> or C<-Ecreate>. Example components are the popular I, an IRC client, I, a web server and I for interacting with a database. =head1 DETAILS OF POE =head2 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: =over 4 =item _stop An anonymous coderef that is called when the POE kernel believes the session has nothing to do. =item ctrl_C A subroutine that is called when the C event is posted. =item _start When the session is first created, the C<_start> method of C<$obj1> will be called. =item load, save These event handlers are mapped to object methods of C<$obj2> that have a different name. The C event will call C<$obj2-Efile_load> and the C event, C<$obj2-Efile_save>. =item _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. =item _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. =back 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 C<$new_event>, which will call C<$method> of C<$object>. It also removes the event handler for C<$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 L will call your handler with the arguement list (C<@_>) 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 ]; # .... } C is the current session, C is the session that posted an event (which could also be the POE kernel). C is the source code file the event was posted from. There are others, consult L. C and C are details specific to this event. If an event can be thought of as a subroutine (which it shouldn't), C, C are the subroutine's parameters. If like me you find this to be cumbersome there are other ways of managing those details. See L and L. =head2 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 C<$event1> to the session reffered to in C<$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 I event. The second line will post an event called C<$event2> to the currently active session. This is also asynchronous event. C can be thought of as syntatic sugar for: $poe_kernel->post( $_[SESSION], $event ); The second line will call the envent handler for C<$event3> in C<$session3> right away, and the event handler's return will be placed into C<@B>. This is a I event. They should be used sparingly, because they break POE's cooperative nature. In all 3 lines passed C<@args> to the POE kernel. These are the details of a given event. They will be available to the event handler as C<$_[ARG0]>, C<$_[ARG1]>... =head2 Sockets Most of the time you will be using POE::Wheels to create and maintain sockets. =head2 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 L. 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' ); =head2 Timers =head2 Aliases There are 3 ways to address a session: with a session object, with a session ID, or with a session alias. =head3 Session object L 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. =head3 Sesssion ID Each session has a unique ID. This ID is avaible via L 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' ); =head3 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'" =head2 Resources =head2 Shutdown I is a common question. The answer is when there are no more sessions. I. 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. I 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. I 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. =head1 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 C list. L can help you avoid this. While C is running, the C<_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 C 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 C event is posted from the wheel. We react by getting rid of the wheel. C<$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 C 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}; } =head1 BEST PRACTICES =head1 AUTHORS Philip Gwyn =head1 SEE ALSO