Tuesday, 17 August 2010

Spread and Perl

If you are here, you have most likely run into the problem of message passing, or interprocess communications, of some form or another.

You want applications to be able to talk to each other ... without writing code that (should) scare you.

The Spread Toolkit is, to quote its homepage, an open source toolkit that provides a high performance messaging service that is resilient to faults across local and wide area networks

Now, that description is perhaps not buzzword, rad fuelled 'boom' sockets on steroids, but Spread has been around for a while, and Ubuntu and Debian comes with packages out of the box. Things I like.

Spread also has bindings for a host of languages, including C, Perl, Ruby and Python. So, as an alternative to 0mq, rabbitmq, etc, possibly requiring less commitment, it may be worth a look.

I'll go through running the daemon on Ubuntu, and a Perl script. It is dead simple, though the Perl Module documentation may obscure that a bit.

Install the Spread daemon


As root (or using sudo)

apt-get install spread libspread-perl

At least in Ubuntu 10.04 LTS, there is a problem with the package at the time of writing where the maintainer has specified the configuration file location incorrectly. Also, since the config file binds to localhost, you need to set the process name with the -n switch for it to run. If not it quits with an error message similar to:

[Tue 17 Aug 2010 14:59:41] Conf_init: My proc id (127.0.1.1) is not in configuration
 Exit caused by Alarm(EXIT)

There is a Ubuntu bug report, but here is a quick
rundown on how to fix this:

  • update /etc/init.d/spread

    change the line:
    DOPTIONS="-c /etc/spread.conf"
    
    to
    DOPTIONS="-c /etc/spread/spread.conf"
    
  • update the /etc/default/spread file to:

    # Change to enable spread
    ENABLED=1
    
    # Options, see spread.1 for list
    OPTIONS="-n localhost"
    
That should be that. You can now start the daemon with
/etc/init.d/spread start

Spread on Perl

The simplest demo I could create was as follows:
1 #!/usr/bin/perl
  2
  3 use strict;
  4 use warnings;
  5
  6 use Spread;
  7
  8 # connect
  9 my ( $mbox, $private_group ) = Spread::connect( {
 10         spread_name  => '4803@localhost',
 11         private_name => 'myname',
 12         group_membership => 0 # we don't want to hear who joins/leaves groups
 13         });
 14 die 'Unable to connect' unless (defined($mbox));
 15
 16 # join a group
 17 Spread::join($mbox, 'mygroup') or die 'Failed to join group';
 18
 19 # multicast message to group
 20 Spread::multicast( $mbox, AGREED_MESS, 'mygroup', 0, "this is your message" );
 21
 22 # poll for messages
 23 my ($messsize);
 24 while ($messsize = Spread::poll($mbox)) {
 25     print "--Next message: $messsize bytes\n";
 26     my ( $service_type, $sender, $groups, $mess_type, $endian, $message ) = Spread::receive($mbox);
 27     print "\tsender : $sender\n";
 28     print "\tgroups : " . join(',', @$groups) . "\n";
 29     print "\tmessage : [$message]\n";
 30 }
 31
 32 Spread::disconnect($mbox);
This should give you the following output:
--Next message: 100 bytes
        sender : #myname#localhost
        groups : mygroup
        message : [this is your message]
If you want to also have a look at the Message join/leave/etc messages, here's the same example slightly modified:
1 #!/usr/bin/perl
  2
  3 use strict;
  4 use warnings;
  5 use Data::Dumper;
  6 use Spread qw(:MESS);
  7
  8 my %types = (
  9     0x00000001 => 'UNRELIABLE_MESS',
 10     0x00000002 => 'RELIABLE_MESS',
 11     0x00000004 => 'FIFO_MESS',
 12     0x00000008 => 'CAUSAL_MESS',
 13     0x00000010 => 'AGREED_MESS',
 14     0x00000020 => 'SAFE_MESS',
 15     0x0000003f => 'REGULAR_MESS',
 16     0x00000040 => 'SELF_DISCARD',
 17     0x00000100 => 'CAUSED_BY_JOIN',
 18     0x00000200 => 'CAUSED_BY_LEAVE',
 19     0x00000400 => 'CAUSED_BY_DISCONNECT',
 20     0x00000800 => 'CAUSED_BY_NETWORK',
 21     0x00001000 => 'REG_MEMB_MESS',
 22     0x00002000 => 'TRANSITION_MESS',
 23     0x00003f00 => 'MEMBERSHIP_MESS',
 24     0x003fc000 => 'RESERVED',
 25     0x00400000 => 'REJECT_MESS',
 26     0x01000000 => 'DROP_RECV',
 27     0x80000080 => 'ENDIAN_RESERVED',
 28            );
 29
 30 # connect
 31 my ( $mbox, $private_group ) = Spread::connect( {
 32         spread_name  => '4803@localhost',
 33         private_name => 'myname',
 34         group_membership => 1
 35         });
 36 die 'Unable to connect' unless (defined($mbox));
 37
 38 # join a group
 39 Spread::join($mbox, 'mygroup') or die 'Failed to join group';
 40
 41 # multicast to group
 42 Spread::multicast( $mbox, AGREED_MESS, 'mygroup', 0, "this is your message" );
 43
 44 # Poll mailbox
 45 my ($messsize);
 46 while ($messsize = Spread::poll($mbox)) {
 47     print "--Next message: $messsize bytes\n";
 48     my ( $service_type, $sender, $groups, $mess_type, $endian, $message ) = Spread::receive($mbox);
 49     print "\tservice_type: $service_type (" .
 50         join('|', map { $types{$_} }  grep { $_ & $service_type } ( keys %types ) ) .
 51         ")\n";
 52     print "\tsender : $sender\n";
 53     print "\tgroups : " . Dumper($groups);
 54     print "\tmess_type : $mess_type\n";
 55     print "\tendian : $endian\n";
 56     $message =~ s/[^[:print:]]/./g;
 57     print "\tmessage : [$message]\n";
 58 }
 59
 60 Spread::disconnect($mbox);
Which outputs
--Next message: 228 bytes
        service_type: 4352 (MEMBERSHIP_MESS|REG_MEMB_MESS|CAUSED_BY_JOIN)
        sender : mygroup
        groups : $VAR1 = [
          '#myname#localhost'
        ];
        mess_type : 0
        endian :
        message : [......jL........#myname#localhost...............]
--Next message: 100 bytes
        service_type: 16 (REGULAR_MESS|AGREED_MESS)
        sender : #myname#localhost
        groups : $VAR1 = [
          'mygroup'
        ];
        mess_type : 0
        endian :
        message : [this is your message]

Note there is binary in the message returned, not handled by the Perl wrapper.

Have fun.

No comments:

Post a Comment