http://qs321.pair.com?node_id=282468

POE is the Perl Object Environment, a cooperative multitasking framework that makes it easy to answer questions like How do I make STDIN time out.

I only started looking at POE this week, because the Documentation for POE? thread piqued my interest. That thread points to several good tutorials on POE,but I thought we should have one here as well. Also, putting this together is helping me figure POE a bit better, and giving me ideas for more fun areas to explore. I think POE and (Tk|Curses) will be next...

POE lets you write programs that handle input from multiple asynchronous sources. Those are big words, but they just mean you'll get the info when it's available, and you don't have to worry about waiting for it. :)

The cooperation is done by creating a set of states, which are invoked by events. Events are generated by input engines (called Wheels), by timers, or by other states.


At the heart of POE lies to POE kernel. It keeps a queue of timed events, and uses the OS's select or poll functionality to watch any file handles or sockets you're interested in for activity. When it's time for an event to fire, or data is available, the associated state handler is invoked. Other event loops are also available, making it possible to have POE programs that have Tk or curses GUIs, for instance.

The sample this tutorial is built around is my answer to the STDIN timeout question asked above. You'll see that it's very easy to write an interactive application in POE with command-line editing and history.

The first step in any POE program is using the POE module. Since POE programs often need to use other modules from the POE:: namespace, you can do

	use POE qw/Wheel::ReadLine Component::Client::HTTP::SSL/;
as a shortcut for
	use POE;
	use POE::Wheel::ReadLine;
	use POE::Component::Client::HTTP::SSL;

In this case, we only need POE::Wheel::ReadLine, which will handle our input requirements.

Each program consists of one or more POE Sessions, which hold a set of states.

#!/usr/bin/perl -w use strict; # Use POE! use POE qw/Wheel::ReadLine/; # Need to do this on Cygwin #$/="\n\r"; # flush STDOUT automatically $|++; POE::Session->create( inline_states => { _start => \&handler_start, gotLine => \&handler_gotLine, prompt => \&handler_prompt, timeout => \&handler_timeout, #_stop => \&handler_stop, } );
In the session's constructor, we specify a hash of state names, and the state handlers associated with them. The subroutines can be named, as they are here, or they can be anonymous sub references.

The _start and _stop states are special; they are invoked by the kernel when the session is created, or just before it's destroyed.

In this case, we don't need to do anything special to handle _stop, so that state is commented out, and the handler isn't implemented.

That means that your _start handler will be invoked before POE::Session->create returns.

The next step is to start the kernel running, and exit the program once it's done.

$poe_kernel->run(); exit;
$poe_kernel is exported by POE automatically.

Of course, we haven't DEFINED any state handlers yet, so our program won't even compile, let alone run.

Every POE state handler is passed a large number of arguments in @_. The most interesting ones are the heap, kernel, and session associated with this state.

The poe heap is just a scalar, which usually is used as a hash reference.

These values can be accessed using an array slice on @_ to initialize scope variables, or explicitly referred to as $_[HEAP], $_[KERNEL], or $_[SESSION]. POE exports HEAP, KERNEL, SESSION, and several other constants automatically.

The first handler we'll see is our start handler:

sub handler_start { my ($kernel, $heap, $session) = @_[KERNEL, HEAP, SESSION]; # POE::Wheel::ReadLine gets you terminal input with command line # history. whenever a line is input, the 'gotLine' event # will run $heap->{wheel} = POE::Wheel::ReadLine->new ( InputEvent => 'gotLine', ); # ask for the prompt event to get run next $kernel->yield('prompt'); }
POE's wheels are modules which handle the hassle of gluing outside event generators, like sockets or file handles, to POE states.

POE::Wheel::ReadLine is a special wheel which invokes states when data is input on the console. It also handles command line editing and history, with a bit of help from us.

Note that we save the wheel in our %{$heap} hash. Otherwise, the wheel would be immediately destroyed, since there would be no outstanding references to it. We'll use that trick later, when it's time to exit. For now, we just associate the wheel's InputEvent with our 'gotLine' state (handled by handler_gotLine). Then, we use "yield" to ask the kernel to schedule the prompt state as soon as possible.

sub handler_prompt { my ($kernel, $heap, $session) = @_[KERNEL, HEAP, SESSION]; print "You have 10 seconds to enter something, or I'll quit!$/"; $heap->{wheel}->get('Type fast: '); # this will make the timeout event fire in 10 seconds $kernel->delay('timeout',10); }
All this handler does is use the get method on our Wheel to prompt the user for input, and then schedule a timeout event in 10 seconds.

Even if this isn't the first time this handler is invoked, calling delay removes the old event, and schedules a new one 10 seconds out.

From here, things are in the hands of the kernel. If the user does nothing, in 10 seconds (or so, timeouts are approximate) the timeout state will be triggered:

sub handler_timeout { my ($kernel, $heap, $session) = @_[KERNEL, HEAP, SESSION]; # taunt (or inform) the user print "You took too long, game over$/"; # setting this to undef will make our Wheel get shutdown # with no wheel, and no pending delay event, the kernel # queue is empty resulting in shutdown $heap->{wheel}=undef; }
When the wheel member is undefined in handler_timeout, the wheel is destroyed, and since there are no pending events and no event sources, the kernel exits.

If the user does enter something, or hits Ctrl-C, the gotLine handler is called.

sub handler_gotLine { my ($kernel, $heap, $session, $arg, $exception) = @_[KERNEL, HEAP, SESSION, ARG0, ARG1]; if(defined $arg) { $heap->{wheel}->addhistory($arg); print "Very good, you entered '$arg'. You get another 10 seconds. +$/"; } else { print "Got input exception '$exception'. Exiting...$/"; # setting this to undef will make our Wheel get shutdown $heap->{wheel}=undef; # setting a delay without a timeout number clears the event $kernel->delay('timeout'); return; } # run the prompt event again to reset timeout and print # new prompt $kernel->yield('prompt'); }
One thing that's interesting here is that we read the ARG0 and ARG1 members of @_. POE passes the arguments last in @_, ranging from ARG0 to $#_. In the case of an InputHandler for this wheel, ARG0 is the text input, and if ARG0 is undef, ARG1 is the exception code.

After "handling" the input, this handler yields back to the prompt handler, which reschedules the timeout and prompts the user again.

I hope this quick walkthru of a simple POE program will help you understand how POE operates. The other tutorials and beginners guides I linked up above are even better, so you should be up and running from state to state in no time. :)

Edit by tye, add READMORE

Replies are listed 'Best First'.
Re: An introduction to POE
by jeffa (Bishop) on Aug 10, 2003 at 14:53 UTC
    Worked fine for me on Linux. My only recomendation would be to import CRLF() from IO::Socket
    use IO::Socket qw(:crlf); local $/ = CRLF; # Cygwin, Linux, Mac, Winders, all
    Or just use the CRLF() constant instead of $/ when you print. Here is a quote from Dr. Stein's Network Progamming With Perl:
    When communicating with a line-oriented network server that uses CRLF to terminate lines, it won't be possible to set $/ to \r\n. Use the explicit string \015\012 instead. To make this less obscure, the Socket and IO::Socket modules ... have an option to export globals named $CRLF and CRLF() that return the correct values.
    (it is very easy to get the \r\n combo backwards ;))

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
      In this case, there really is no "backwards". I really wanted a carriage return and a line feed, the order wasn't important.

      But thanks for the portability hints.
      --
      Mike

Re: An introduction to POE
by Coruscate (Sexton) on Aug 10, 2003 at 09:14 UTC

    hrmm... your script fails horribly for me. It freezes after I type the first character. The 'Type fast:' prompt is also right-flushed on my terminal. Uncommenting your cygwin line fixes the right-flushed text, but that is a bad way to do it. $/ is best kept at "\n". Try using \015\012 instead of \r\n as well. "\r\n" and "\015\012" are not guaranteed to be the same value. So to beat the snot out of POE for once (and most likely only time) in my life heh:

    #!/usr/bin/perl -w $|++; use strict; my ($input, $failed); STARTIT: eval { local $SIG{ALRM} = sub { die "timed out\n" }; print 'You have 10 seconds. Type fast: '; alarm 10; chomp( $input = <STDIN> ); alarm 0; }; if ($@) { if ($@ eq "timed out\n") { if (++$failed == 3) { print "\nTimed out 3 times. You're toast!\n\n" and exit; } else { print "\nTimed out. Try again!\n\n"; } } else { die "Unhandled error: $@"; } } else { print "You said '$input'. 10 more seconds now!\n\n"; } goto STARTIT;


    If the above content is missing any vital points or you feel that any of the information is misleading, incorrect or irrelevant, please feel free to downvote the post. At the same time, please reply to this node or /msg me to inform me as to what is wrong with the post, so that I may update the node to the best of my ability.

      What platform did it freeze on? You may want to file a bug report.

      I'm not sure what the behaviour of \n\r will be on different platforms.

      I'm QUITE sure that using \012\015 is a bad idea. What if the poor user is running on a EBCDIC system? \n\r has a chance of doing the right thing, \012\015 doesn't.

      You solution with signals does answer the original question, but the point wasn't to solve that problem perfectly, it was to introduce POE. Besides, you didn't implement command-line history or editing :)) (j/k)
      --
      Mike

        I'm QUITE sure that using \012\015 is a bad idea ... \n\r has a chance of doing the right thing.

        Um, I don't think so - Read the perlport man page under the heading "Newlines". To quote:

        A common misconception in socket programming is that "\n" eq "\012" everywhere. When using protocols such as common Internet protocols, "\012" and "\015" are called for specifically, and the values of the logical "\n" and "\r" (carriage return) are not reliable.

        print SOCKET "Hi there, client!\r\n"; # WRONG print SOCKET "Hi there, client!\015\012"; # RIGHT

        This is equally applicable to the use of the literal \r and \n in general programming.

         

        perl -le 'print+unpack"N",pack"B32","00000000000000000000001001111000"'

        Some versions of Linux don't seem to get along with non-blocking ReadKey. I received a test case about it in e-mail on 23 August:

        #!/usr/bin/perl use Term::ReadKey; ReadMode 5; # Turn off controls keys while (1) { while (not defined ($key = ReadKey(-1))) { print "Foo\n"; sleep (1); } print "Get key $key\n"; ($key eq 'q') and last; } ReadMode 0; # Reset tty mode before exiting

        If the test case fails for you, please e-mail a note to bug-POE@rt.cpan.org so (a) I'll see it, and (b) I won't forget it. :) Since it's a platform-dependent problem, it would help a lot if output from uname -a and perl -V were included. Thanks.

        -- Rocco Caputo - rcaputo@pobox.com - poe.perl.org

Re: An introduction to POE
by NetWallah (Canon) on Aug 10, 2003 at 15:14 UTC
    I'm having trouble getting this sample POE code to work under Win32 (XP) with Activestate Perl 5.8.0. I have installed the POE, TermReadKey, and Term-Cap modules. Here is the error I get:
    >perl poetest.pl Your vendor has not defined POSIX macro B38400, used at D:/Perl/site/l +ib/POE/Whe el/ReadLine.pm line 89 Compilation failed in require at (eval 31) line 1. BEGIN failed--compilation aborted at (eval 31) line 1. could not import qw(Wheel::ReadLine) at poetest.pl line 5 BEGIN failed--compilation aborted at poetest.pl line 5. E:\Documents and Settings\vijay\test>perl -v
    Seems like I may need to install Cygwin. But I'm curious as to what this macro at line 89 is, and if there is a workaround for Win32 - here is the relevant code from the ReadLine.pm module:
    Line 12: use POSIX qw(B38400); Line 88: # Get the terminal speed for Term::Cap. my $ospeed = B38400;
      Seems like I may need to install Cygwin. But I'm curious as to what this macro at line 89 is, and if there is a workaround for Win32

      The B38400 constant is exported by the standard POSIX module. It represents 38400 baud terminal rates, the fastest available under POSIX. Windows doesn't use standard terminals, so B38400 is useless in ActivePerl. Unfortunately, ActivePerl doesn't support B38400 with a dummy value, so code relying on it breaks there.

      The problem is easy enough to work around if the author knows about it. Just check $^O eq 'MSWin32' and either avoid B38400 altogether or define a dummy version of it so the code continues on.

      Check rt.cpan.org for an effective way to report bugs in CPAN distributions.

      -- Rocco Caputo - rcaputo@pobox.com - poe.perl.org

Re: An introduction to POE
by bsb (Priest) on Aug 12, 2003 at 13:52 UTC
    Events are generated by input engines (called Wheels)...

    Why "Wheels"? This has puzzled me for a while.

      The explanation is cute, but fairly non-obvious (from Evolution of a POE Server, on the POE site):

      The POE based server we just completed is still a bunch of work. What's worse, most of the work never changes from one server to the next. Listening to a server socket and accepting connections from it is a well-established science. Likewise, performing buffered operations on non-blocking sockets is largely the same everywhere. Reinventing these wheels for every server gets old very fast.

      We created a group of classes under the POE::Wheel namespace to encapsulate these standard algorithms. Each Wheel contains some initialization code to set up event generators, and each implements the handlers for those events.
      --
      Mike