POE doesn't provide medium- and high-level UDP socket classes because they're trivial to work with, and nobody who does significant UDP work has published something. Perhaps this is your chance to release a CPAN module. ;)

A brief introduction to UDP and datagrams is in order. People who understand them can just skip down to the code.

UDP sockets are used to transfer datagrams, which behave quite unlike TCP's streams. For starters, datagrams have no concept of a "connection", so there's no delay while one negotiates with a remote socket. Datagrams are not guaranteed to be received in the order they were sent. In fact, datagrams are not guaranteed to be received at all.

What's all that mean for us? A few things.

First, we can create UDP server or client sockets without delay. There is no connection phase to block our programs.

Second, UDP sockets don't block when sending or receiving datagrams. Their inherent lossiness means the OS will just drop packets rather than force us to wait before sending more.

Finally, UDP is stateless unless you put some work into it. Every datagram includes the sender's socket address. Since there's no connection, we can't just send() something "back". Instead, we must send it back to its source address. (We know about connect() setting the default address and choose to ignore it here.)

So the server code. We create a single session with two event handlers.

The _start handler starts up the server. It creates a UDP server socket and uses select_read() to notify us whenever a datagram is waiting.

The got_datagram handler receives the datagram and its return address. It displays something so we know it's working, encrypts the message using the venerable (or is that vulnerable?) ROT13 cipher, and sends it back whence it came.

#!/usr/bin/perl
use warnings;
use strict;
use POE;
use IO::Socket::INET;
use constant DATAGRAM_MAXLEN => 1024;
POE::Session->create(
  inline_states => {
    _start       => \&server_start,
    get_datagram => \&server_read,
  }
);
POE::Kernel->run();
exit;

sub server_start {
  my $kernel = $_[KERNEL];
  my $socket = IO::Socket::INET->new(
    Proto     => 'udp',
    LocalPort => '8675',
  );
  die "Couldn't create server socket: $!" unless $socket;
  $kernel->select_read($socket, "get_datagram");
}

sub server_read {
  my ($kernel, $socket) = @_[KERNEL, ARG0];
  my $remote_address = recv($socket, my $message = "", DATAGRAM_MAXLEN, 0);
  return unless defined $remote_address;
  my ($peer_port, $peer_addr) = unpack_sockaddr_in($remote_address);
  my $human_addr = inet_ntoa($peer_addr);
  print "(server) $human_addr : $peer_port sent us $message\n";
  $message =~ tr[a-zA-Z][n-za-mN-ZA-M];
  send($socket, $message, 0, $remote_address) == length($message)
    or warn "Trouble sending response: $!";
}


The client is virtually identical. Key differences:

#!/usr/bin/perl
use warnings;
use strict;
use POE;
use IO::Socket::INET;
use constant DATAGRAM_MAXLEN => 1024;
POE::Session->create(
  inline_states => {
    _start       => \&client_start,
    get_datagram => \&client_read,
  }
);
POE::Kernel->run();
exit;

sub client_start {
  my $kernel = $_[KERNEL];
  my $socket = IO::Socket::INET->new(Proto => 'udp',);
  die "Couldn't create client socket: $!" unless $socket;
  $kernel->select_read($socket, "get_datagram");
  my $message = "This is a test.";
  print "Sending '$message'\n";
  my $server_address = pack_sockaddr_in(8675, inet_aton("127.0.0.1"));
  send($socket, $message, 0, $server_address) == length($message)
    or die "Trouble sending message: $!";
}

sub client_read {
  my ($kernel, $socket) = @_[KERNEL, ARG0];
  my $remote_address = recv($socket, my $message = "", DATAGRAM_MAXLEN, 0);
  return unless defined $remote_address;
  my ($peer_port, $peer_addr) = unpack_sockaddr_in($remote_address);
  my $human_addr = inet_ntoa($peer_addr);
  print "(client) $human_addr : $peer_port sent us $message\n";
  send($socket, $message, 0, $remote_address) == length($message)
    or warn "Trouble sending response: $!";
}