udp_server with IO::Lambda

I'm turning again to my module IO::Lambda, which I think, doesn't get the resonance it could have. I've seen AnyEvent's tcp_server, which is great and easy to use, and was reluctant to add one to IO::Lambda, because it's so easy. Or at least subjectively so :)

But I'm thinking, well, it's one thing to not add it into common API for whatever reasons, but if it's simple enough, why not make it into an educational example? So I wrote tcp_server and udp_server, just for kicks, and indeed they're simple, just not that simple so they don't need any comments in the code.

So I'm starting with upd_server, which is the simplest of the two, a plain UDP server script that listens on port 5000 and prints whatever data it receives. To test it, fire it up in one terminal, and run 'nc -u localhost 5000' (nc is netcat on my machine, could be something else on yours) in another, and type something there. The UDP server should just repeat the same.

Danger! If the code below causes you to wtf more profusely than usual -- like what? lambda? recv with {} and not with ()? well, I could've sent you to the manual, but it's rather big anyway and hard to stomach. Let's just say that lambda {} encapsulates a sequence of non-blocking actions into an object, where each action is a closure, that can further call a closure etc etc. Please try to bear with the code style, and then, only if impossible, vent it out to the comments ))

#!env perl
use strict;
use warnings;
use IO::Socket;
use IO::Lambda qw(:all);
use IO::Lambda::Socket qw(:all);

# Returns a lambda that listens on an UDP socket, and spawns
# new lambdas on every datagram.
sub udp_server(&@)
{
    my ($callback, $port) = @_;

    my $server = IO::Socket::INET-> new(
        Proto => 'udp',
        LocalPort => $port,
        Blocking => 0,
    );
    return $! unless $server;

    # this lambda will be the server
    
return lambda {

        # The two lines below say: on the socket $server, sleep
        # until the socket becomes writable, which means that we've
        # got a connection. On the connected socket then issue a
        # CORE::recv() call with max UDP packet size 64K
        
context $server, 65536, 0;
    recv {

        # Address of the client socket, and the datagram
        
my ( $addr, $msg) = @_;

        # Re-register the callback and wait again (i.e. same context $server; recv ) call
        
again;

        # Instantiate a new lambda with $addr and $msg, and wait
        # for it to finish. It's not a blocking wait, and again() call
        # above makes sure that if there comes another connection meanwhile,
        # it will be served as well.
        
context $callback-> (), $addr, $msg;
        &tail();
    }}
}

# create a new lambda that does nothing, just a debug print
sub handle_incoming_connection_udp
{

    lambda {
        my ( $addr, $msg ) = @_;
        my ($port, $iaddr) = sockaddr_in($addr);
        my $host = inet_ntoa($iaddr);

        print "got msg '$msg' from $host:$port\n";
    }
}

# fire up the server
my $server = udp_server { handle_incoming_connection_udp } 5000;
die $server unless ref $server;
$server-> wait;

download


Note that even though handle_incoming_connection_udp doesn't do a thing, it's not necesary should be that simple. It can do any other non-blocking operation that IO::Lambda is capable of -- and it is capable of many interesting things. I'll expand on it in the next entry about tcp_server.

(also: please tell me if there are places in the text and/or code you don't grok -- I'll try to work on them)

Leave a comment

About Dmitry Karasik

user-pic I blog about Perl.