
![]() | ![]() |
17.15. Writing a Multitasking Server with POE
17.15.1. Problem
You
want to write a server that handles multiple clients from within the
one process, without using Perl 5.8's threads or the complexity of
non-blocking I/O.
17.15.2. Solution
Use the cooperative multitasking framework POE (available from CPAN)
and the accompanying POE::Component::Server::TCP module to create the
server for you:#!/usr/bin/perl
use warnings;
use strict;
use POE qw(Component::Server::TCP);
# Start a TCP server. Client input will be logged to the console and
# echoed back to the client, one line at a time.
POE::Component::Server::TCP->new
( Port => $PORT_NUMBER, # port to listen on
ClientInput => \&handle_input, # method to call with input
);
# Start the server.
$poe_kernel->run( );
exit 0;
sub handle_input {
my ( $session, $heap, $input ) = @_[ SESSION, HEAP, ARG0 ];
# $session is a POE::Session object unique to this connection,
# $heap is this connection's between-callback storage.
# New data from client is in $input. Newlines are removed.
# To echo input back to the client, simply say:
$heap->{client}->put($input);
# and log it to the console
print "client ", $session->ID, ": $input\n";
}
17.15.3. Solution
POE is a cooperatively multitasking framework for Perl built entirely
out of software components. POE doesn't require you to recompile the
Perl interpreter to support threads, but it does require you to
design your program around the ideas of events and callbacks.
Documentation for this framework is available at http://poe.perl.org/.It helps to think of POE as an operating system: there's the kernel
(an object responsible for deciding which piece of code is run next)
and your processes (called sessions, implemented
as objects). POE stores the kernel object in the variable
$poe_kernel, which is automatically imported into
your namespace. Each process in your operating system has a
heap, memory where the variables for that
process are stored. Sessions have heaps as well. In an operating
system, I/O libraries handle buffered I/O. In POE, a
wheel handles accepting data from a writer and
sending it on to a reader.There are dozens of prebuilt sessions (called
components) for servers, clients, parsers,
queues, databases, and many other common tasks. These components do
the hard work of understanding the protocols and data formats,
leaving you to write only the interesting code—what to do with
the data or what data to serve.When you use POE::Component::Server::TCP, the component handles
creating the server, listening, accepting connections, and receiving
data from the client. For each bit of data it receives, the component
calls back to your code. Your code is responsible for parsing the
request and generating a response.In the call to POE::Component::Server::TCP's constructor, specify the
port to listen on with Port, and your code to
handle input with ClientInput. There are many
other options and callbacks available, including
Address to specify a particular interface address
to listen on and ClientFilter to change its
default line parser.Your client input subroutine is called with several parameters, but
we use only three: the POE session object representing this
connection, the heap for this session, and the latest chunk of input
from the client. The first two are standard parameters supplied by
POE to all session calls, and the last is supplied by the server
component.The strange assignment line at the start of
handle_input merely takes a slice of
@_, using constants to identify the position in
the method arguments of the session, heap, and first real argument.
It's a POE idiom that lets the POE kernel change the actual method
parameters and their order, without messing up code that was written
before such a change.my ( $session, $heap, $input ) = @_[ SESSION, HEAP, ARG0 ];
The session's heap contains a client shell that you use for
communicating with the client: $heap->{client}.
The put method on that object sends data back to
the client. The client's IP address is accessible through
$heap->{remote_ip}.If the action you want to perform in the callback is time-consuming
and would slow down communication with other clients that are
connected to your server, you may want to use POE sessions. A session
is an event-driven machine: you break the time-consuming task into
smaller (presumably quicker) chunks, each of which is implemented as
a callback. Each callback has one or more events that trigger it.It's the responsibility of each callback to tell the kernel to queue
more events, which in turn pass execution to the next callback (e.g.,
in the "connect to the database" function, you'd tell the kernel to
call the "fetch data from the database" function when you're done).
If the action cannot be broken up, it can still be executed
asynchronously in another process with POE::Wheel::Run or
POE::Component::Child.POE includes non-blocking timers, I/O watchers, and other resources
that you can use to trigger callbacks on external conditions. Wheels
and Components are ultimately built from these basic resources.Information on POE programming is given at http://poe.perl.org, including pointers to
tutorials given at various conferences. It can take a bit of mental
adjustment to get used to the POE framework, but for programs that
deal with asynchronous events (such as GUIs and network servers) it's
hard to beat POE for portability and functionality.
17.15.4. See Also
The documentation for the CPAN modules POE, POE::Session, POE::Wheel,
and POE::Component::Server::TCP; Recipe 17.14
![]() | ![]() | ![]() |
17.14. Multitasking Server with Threads | ![]() | 17.16. Writing a Multihomed Server |

Copyright © 2003 O'Reilly & Associates. All rights reserved.