Perl CookbookPerl CookbookSearch this book

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; http://poe.perl.org/; Recipe 17.14



Library Navigation Links

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