=head1 Message Passing for the Non-Blocked Mind =head2 Introduction and Terminology This is a tutorial about how to get the swing of the new L module. Which allows us to transparently pass messages to our own process and to other process on other or the same host. What kind of messages? Well, basically a message here means a list of Perl strings, numbers, hashes and arrays, mostly everything that can be expressed in a L text (as JSON is used by default in the protocol). And next you might ask: between which entities are those messages being "passed"? Basically between C, which are basically your applications (as in processes) that use L that run either on the same or different hosts. In this tutorial I'll show you how to write a simple chat server based on L. =head2 The Chat Client OK, lets start by implementing the "frontend" of the client. We will delay the explanation and the code of the server until we finished the client, as the most complex things actually happen in the client. We will use L to do non-blocking IO read on standard input: #!perl use AnyEvent; use AnyEvent::Handle; sub send_message { die "This is where we will send the messages to the server" . "in the next step of this tutorial.\n" } # make an AnyEvent condition variable for the 'quit' condition # (when we want to exit the client). my $quit_cv = AnyEvent->condvar; my $stdin_hdl = AnyEvent::Handle->new ( fh => \*STDIN, on_read => sub { my ($hdl) = @_; $hdl->push_read (line => sub { my ($hdl, $line) = @_; if ($line =~ /^\/quit/) { # /quit will end the client $quit_cv->send; } else { send_message ($line); } }); } ); $quit_cv->recv; This is now a very basic client. Explaining explicitly what L does or what a I is all about is out of scope of this document, please consult L or the manual pages for L and L. =head2 First Step Into Messaging Now we take a look at L. We need to know what to do in C. This is an example of how it might look like: ... # the use lines from the above snippet use AnyEvent::MP; sub send_message { my ($msg) = @_; snd $server_port, message => $msg; } ... # the rest of the above script The C function is exported by L, it stands for 'send a message'. The first argument is the I (a I is something that can receive messages) of the server which will receive the message . How we get it will be explained in the next step. The next arguments of C are C and C<$msg> are the first two elements of the I (a I in L is a be a simple list of values, which can be sent to I). Thats all fine so far, but how do we get the C<$server_port>? Well, we will need to get the unique I of the server's port where he wants to receive all the incoming chat messages. A I is unfortunately a very unique string, which we are unable to know in advance. But L supports the concept of 'well known ports', which is basically a port on the server side registered under a well known name. For example, the server has a port for receiving chat messages with a unique I and registered it under the name C. BTW, these "well known port names" should follow similar rules as Perl identifiers, so you should prefix them with your package/module name to make them unique, unless you use them in the main program. As I can only be sent to a I and not just to a name we have to ask the server I what I has the well known port with the name C. Another new term, what is a I: The messaging network that can be created with L consists of I. A I handles all the connection and low level message sending logic for its application. The application in this case is the server. Also every client has/is a I. =head2 Getting The Chatter Port Ok, lots of talk, now some code. Now we will actually get the C<$server_port> from the backend: ... use AnyEvent::MP; my $resolved_cv = AnyEvent->condvar; my $client_port = create_port; my $server_node = "localhost:1299#"; snd $server_node, wkp => "chatter", "$client_port", "resolved"; my $server_port; # setup a receiver callback for the 'resolved' message: $client_port->rcv (resolved => sub { my ($client_port, $type, $chatter_port_id) = @_; print "Resolved the server port 'chatter' to $chatter_port_id\n"; $server_port = $chatter_port_id; $resolved_cv->send; 1 }); # lets block the client until we resolved the server port. $resolved_cv->recv; # now setup another receiver callback for the chat messages: $client_port->rcv (message => sub { my ($client_port, $type, $msg) = @_; print "chat> $msg\n"; 0 }); # send the server a 'join' message: snd $server_port, join => "$client_port"; sub send_message { ... Now that was a lot of new stuff. In order to ask the server and receive an answer we need to have a I where we can receive the answer. This is what C will do for us, it just creates a new local port and returns us an object (that will btw. stringify to the I), that we can use to receive messages. Next thing is the C<$server_node>. In order to refer to another node we need some kind of string to reference it. The I is basically a comma seperated list of C pairs. We assume in this tutorial that the server runs on your localhost at port 1299, this gives us the noderef C. Now you might ask what the C<#> at the end in C<$server_node> the above example is about. Well, what I didn't tell you yet is that each I has a default I to receive messages. The default port is the empty string C<"">. The I of a I also provides some special services for us, for example resolving a well known port to a I. Now to this line: snd $server_node, wkp => "chatter", "$client_port", "resolved"; We send a message with first element being C (standing for 'well known port'). Then the well known port name that we want to resolve to a I: C. And in order for the server node to be able to send us back the resolved I we have to tell it where to send the result message: The result message will have as it's first argument the string C and will be sent to C<$client_port> (the I of our own just created port). Next comes the receiver for this C request. $client_port->rcv (resolved => sub { my ($client_port, $type, $chatter_port_id) = @_; ... 1 }); This sets up a receiver on our own port for the result message with the first argument being the string C. Receivers can match the contents of the messages before actually 'sending' it to the given callback. B that the given callback has to return either a true or a false value for indicating whether it is B (true value) or still wants to B (false value) receiving messages. In this case we tell the C<$client_port> to look into the received messages and look for the string C in the first element of the message. If it is found, the given callback will be called with the C<$client_port> as first argument, and the message as the remaining arguments. We name the first element of the message C<$type> in this case. It's a common idiom to code the 'type' of a message into it's first element, this allows for simple matching. The result message will contain the I of the well known port C as next element, and will be put in C<$chatter_port_id>. Next we just assign C<$server_port> and return a 1 (a true value) from the callback. It indicates that we are done and don't want to receive further C messages with this callback. Now we continue to the rest of the client by calling C on C<$resolved_cv>. First new step after this is setting up the chat message receiver callback. $client_port->rcv (message => sub { my ($client_port, $type, $msg) = @_; print "chat> $msg\n"; 0 }); We assume that all messages that are broadcast to all clients by the server will contain the string C as first element, and the actual message as second element. The callback returns a false value this time, to indicate that it wants to continue receiving messages. Last but not least we actually tell the server to send us the new chat messages from other clients. We do so by sending the message type C followed by our own I. # send the server a 'join' message: snd $server_port, join => "$client_port"; Then the server knows where to send all the new messages to. =head2 The Finished Client This is the complete client script: #!perl use AnyEvent; use AnyEvent::Handle; use AnyEvent::MP; my $resolved_cv = AnyEvent->condvar; my $client_port = create_port; my $server_node = "localhost:1299#"; snd $server_node, wkp => "chatter", "$client_port", "resolved"; my $server_port; # setup a receiver callback for the 'resolved' message: $client_port->rcv (resolved => sub { my ($client_port, $type, $chatter_port_id) = @_; print "Resolved the server port 'chatter' to $chatter_port_id\n"; $server_port = $chatter_port_id; $resolved_cv->send; 1 }); # lets block the client until we resolved the server port. $resolved_cv->recv; # now setup another receiver callback for the chat messages: $client_port->rcv (message => sub { my ($client_port, $type, $msg) = @_; print "chat> $msg\n"; 0 }); # send the server a 'join' message: snd $server_port, join => "$client_port"; sub send_message { my ($msg) = @_; snd $server_port, message => $msg; } # make an AnyEvent condition variable for the 'quit' condition # (when we want to exit the client). my $quit_cv = AnyEvent->condvar; my $stdin_hdl = AnyEvent::Handle->new ( fh => \*STDIN, on_read => sub { my ($hdl) = @_; $hdl->push_read (line => sub { my ($hdl, $line) = @_; if ($line =~ /^\/quit/) { # /quit will end the client $quit_cv->send; } else { send_message ($line); } }); } ); $quit_cv->recv; =head2 The Server Ok, now finally to the server. What do we need? Well, we need to setup the well known port C where all clients send their messages to. Up and into code right now: #!perl use AnyEvent; use AnyEvent::MP; become_public "localhost:1299"; my $chatter_port = create_port; $chatter_port->register ("chatter"); my %client_ports; $chatter_port->rcv (join => sub { my ($chatter_port, $type, $client_port) = @_; print "got new client port: $client_port\n"; $client_ports{$client_port} = 1; 0 }); $chatter_port->rcv (message => sub { my ($chatter_port, $type, $msg) = @_; print "message> $msg\n"; snd $_, message => $msg for keys %client_ports; 0 }); AnyEvent->condvar->recv; This is all. Looks much easier, doesn't it? I'll explain it only shortly, as we had the discussion of the C method in the client part of this tutorial above. First this: become_public "localhost:1299"; This will tell our I to become a I node, which means that it can be contacted via TCP. The first argument should be the I the server wants to be reachable at. In this case it's the TCP port 1299 on localhost. Next we bascially setup two receivers, one for the C messages and another one for the actual messages of type C. In the C message we get the client's port, which we just remember in the C<%client_ports> hash. In the receiver for the message type C we will just iterate through all known C<%client_ports> and relay the message to them. And thats it. =head2 The Remaining Problems The shown implementation still has some bugs. For instance: How does the server know that the client isn't there anymore, and can cleanup the C<%client_ports> hash? And also the chat messages have no originator, so we don't know who actually sent the message (which would be quite useful for human-to-human interaction: to know who the other one is :). But aside from these issues I hope this tutorial got you the swing of L and explained some common idioms. =head1 SEE ALSO L L L =head1 AUTHOR Robin Redeker