During the evolution of this simple echo server, we've managed to reduce the server from about 130 lines to about 75. In the process, we've whittled away the main loop and a lot of the code for dealing with sockets. In its place, we've added code that manages POE::Wheel objects instead.
It turns out that managing POE::Wheel
objects is only a
little less tedious than writing servers longhand. Our servers still
must set up SocketFactory instances to listen on sockets (see POE::Wheel::SocketFactory),
they still
must create ReadWrite
wheels to interact with clients, and they
still must handle errors. Even with wheels, these things happen
pretty much the same way for every server, and they're just the
coding overhead necessary before sitting down to write the fun
stuff.
As with wheels, we've abstracted the repetitive work into something larger. In this case, Ann Barcomb designed a server component to manage the wheels and other things for us. Nearly all of the tedious overhead is gone.
As usual, we set up the Perl program by loading the modules we'll need.
#!/usr/bin/perl use warnings; use strict; use POE qw(Component::Server::TCP); |
Next we create and run the TCP server component. It will listen on port 12345, and it will handle all the boring tasks of accepting connections, managing wheels, and so forth.
POE::Component::Server::TCP is customizable through callbacks. In its simplest usage, we only need to supply the function to handle input.
POE::Component::Server::TCP->new ( Port => 12345, ClientInput => \&client_input, ); POE::Kernel->run(); exit; |
POE::Component::Server::TCP has a default mode where it accepts new connections and creates the sessions to handle them. At creation time, POE::Component::Server::TCP starts one POE::Session to listen for new connections.
new()
starts a server based on POE::Component::Server::TCP
and returns a session ID for the master listening session. All error
handling is done within the server, via the Error
and ClientError
callbacks.
The server may be shut down by posting a shutdown
event to
the master session, either by its ID
or the name given to it by the
Alias
parameter.
POE::Component::Server::TCP does a lot of work in its constructor. The design goal is to push as much overhead into one-time construction so that ongoing run-time has less overhead. Because of this, the server's constructor can take quite a daunting number of parameters.
POE::Component::Server::TCP
always returns a
POE::Session
ID
for the session
that will be listening for new connections.
Finally we define the input handler.
Every client connection has been given its own POE::Session
instance, so each has its own heap to store things in. This simplifies
our code because their heaps track things for each connection, not
us. Each connection's ReadWrite
wheel is already placed into
$heap->{client}
for us.
sub client_input { my ( $heap, $input ) = @_[ HEAP, ARG0 ]; $heap->{client}->put($input); } |
And, uh, that's all. Our simple echo server is now under 20 lines, most of which deal with the aspects that make it unique.
Here is the full listing:
$ cat -n componentserver.pl 1 #!/usr/bin/perl 2 use warnings; 3 use strict; 4 use POE qw(Component::Server::TCP); 5 POE::Component::Server::TCP->new( 6 Port => 12345, 7 ClientInput => \&client_input, 8 ); 9 POE::Kernel->run(); 10 exit; 11 12 sub client_input { 13 my ($heap, $input) = @_[HEAP, ARG0]; 14 $heap->{client}->put("ECHO: $input"); 15 }
And here is an example of execution:
$ ./componentserver.pl |
$ gnetcat localhost 12345 hola! ECHO: hola! que tal? ECHO: que tal? ^C |
Casiano Rodríguez León