ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MP/MP/Intro.pod
Revision: 1.16
Committed: Wed Aug 26 14:02:11 2009 UTC (14 years, 9 months ago) by elmex
Branch: MAIN
Changes since 1.15: +55 -9 lines
Log Message:
added another chapter to the intro.

File Contents

# User Rev Content
1 root 1.4 =head1 Message Passing for the Non-Blocked Mind
2 elmex 1.1
3 root 1.8 =head1 Introduction and Terminology
4 elmex 1.1
5 root 1.4 This is a tutorial about how to get the swing of the new L<AnyEvent::MP>
6 root 1.14 module, which allows us to transparently pass messages to our own process
7 root 1.8 and to other processes on another or the same host.
8 elmex 1.1
9 root 1.15 What kind of messages? Well, basically a message here means a list of Perl
10     strings, numbers, hashes and arrays, anything that can be expressed as a
11     L<JSON> text (as JSON is used by default in the protocol).
12 elmex 1.1
13 root 1.15 And next you might ask: between which entities are those messages being
14     "passed"? Physically within or between I<nodes>: a nodes is basically a
15 root 1.8 process/program that use L<AnyEvent::MP> and can run either on the same or
16     different hosts.
17 elmex 1.1
18 elmex 1.16 To make this more manageable, every node can contain any number of
19 root 1.9 I<ports>: Ports are ultimately the receivers of your messages.
20    
21 elmex 1.16 In this tutorial I'll show you how to write a simple chat server based on
22     L<AnyEvent::MP>. This example is used because it nicely shows how to organise a
23     simple application, but keep in mind that every node trusts any other, so this
24     chat cannot be used to implement a real public chat server and client system,
25     but it can be used to implement a distributed chat server for example.
26 elmex 1.1
27 root 1.13 =head1 System Requirements and System Setup
28 elmex 1.7
29     Before we can start we have to make sure some things work on your
30 root 1.8 system.
31 elmex 1.7
32 root 1.14 You should of course first make sure that L<AnyEvent> and L<AnyEvent::MP>
33 elmex 1.7 are installed. But how to do that is out of scope of this tutorial.
34    
35 root 1.13 Then we have to setup a I<shared secret>: for two L<AnyEvent::MP> nodes to
36 root 1.8 be able to communicate with each other and authenticate each other it is
37 elmex 1.16 necessary to setup the same I<shared secret> for both of them (or use TLS
38 root 1.14 certificates).
39 root 1.12
40 root 1.14 The easiest way is to set this up is to use the F<aemp> utility:
41 root 1.12
42     aemp gensecret
43    
44 root 1.14 This creates a F<$HOME/.perl-anyevent-mp> config file and generates a
45 root 1.12 random shared secret. You can copy this file to any other system and then
46 root 1.13 communicate with it. You can also select your own shared secret (F<aemp
47     setsecret>) and for increased security requirements you can even create
48 root 1.14 a TLS certificate (F<aemp gencert>), causing connections to not just be
49 root 1.13 authenticated, but also to be encrypted.
50 root 1.12
51     Connections will only be successful when the nodes that want to connect to
52     each other have the same I<shared secret> (or successfully verify the TLS
53 root 1.14 certificate of the other side).
54 elmex 1.7
55     B<If something does not work as expected, and for example tcpdump shows
56 elmex 1.16 that the connections are closed almost immediately, you should make sure
57 root 1.12 that F<~/.perl-anyevent-mp> is the same on all hosts/user accounts that
58     you try to connect with each other!>
59 root 1.8
60 elmex 1.16 =head1 Passing Your First Message
61    
62     As start lets have a look at the messaging API. The next example is just a
63     demo to show the basic elements of message passing with L<AnyEvent::MP>.
64     It shout just print: "Ending with: 123". So here the code:
65    
66     use AnyEvent;
67     use AnyEvent::MP;
68    
69     initialise_node;
70    
71     my $end_cv = AnyEvent->condvar;
72    
73     my $port = port;
74    
75     rcv $port, test => sub {
76     my ($data) = @_;
77     $end_cv->send ($data);
78     };
79    
80     snd $port, test => 123;
81    
82     print "Ending with: " . $end_cv->recv . "\n";
83    
84     It already contains lots of the API that we are going to use. First there
85     is C<initialise_node>, which will initialise the L<AnyEvent::MP> node for that
86     process.
87    
88     Next there is the C<port> function which will create a I<port id> for us, where
89     we can wait for messages and send messages to. The port id is a simple string,
90     which acts as identifier for a port, with the form C<noderef#portname>. The
91     I<noderef> is basically a string that refers to the node and also contains
92     enough information to contact the node from the outside. The I<portname> is
93     usually just a random string.
94    
95     Next the call to C<rcv> sets up a receiver callback. The first element in
96     every message is usually denoting it's I<type> or I<tag>. Which should be a
97     simple string, the second argument to C<rcv> lets us match the tag of a
98     message. If it matches, the callback will be called, with the remaining
99     elements of the message as arguments.
100    
101     C<snd> sends a message. The message consists of two elements: The string
102     C<'test'> and the number C<123>.
103    
104     The message arrives in the callback we setup with C<rcv> and will trigger the
105     condition variable C<$end_cv> to deliver the result value and end the program.
106    
107 root 1.8 =head1 The Chat Client
108    
109     OK, lets start by implementing the "frontend" of the client. We will
110     develop the client first and postpone the server for later, as the most
111     complex things actually happen in the client.
112    
113     We will use L<AnyEvent::Handle> to do non-blocking IO read on standard
114     input (all of this code deals with actually handling user input, no
115     message passing yet):
116 elmex 1.7
117 root 1.8 #!perl
118 elmex 1.1
119     use AnyEvent;
120     use AnyEvent::Handle;
121    
122     sub send_message {
123     die "This is where we will send the messages to the server"
124     . "in the next step of this tutorial.\n"
125     }
126    
127     # make an AnyEvent condition variable for the 'quit' condition
128     # (when we want to exit the client).
129     my $quit_cv = AnyEvent->condvar;
130    
131     my $stdin_hdl = AnyEvent::Handle->new (
132 root 1.8 fh => *STDIN,
133     on_error => sub { $quit_cv->send },
134     on_read => sub {
135 elmex 1.1 my ($hdl) = @_;
136    
137     $hdl->push_read (line => sub {
138     my ($hdl, $line) = @_;
139    
140     if ($line =~ /^\/quit/) { # /quit will end the client
141     $quit_cv->send;
142     } else {
143     send_message ($line);
144     }
145     });
146     }
147     );
148    
149     $quit_cv->recv;
150    
151 root 1.4 This is now a very basic client. Explaining explicitly what
152     L<AnyEvent::Handle> does or what a I<condvar> is all about is out of scope
153     of this document, please consult L<AnyEvent::Intro> or the manual pages
154     for L<AnyEvent> and L<AnyEvent::Handle>.
155 elmex 1.1
156 root 1.9 =head1 First Steps Into Messaging
157 elmex 1.1
158 root 1.8 To supply the C<send_message> function we now take a look at
159     L<AnyEvent::MP>. This is an example of how it might look like:
160 elmex 1.1
161     ... # the use lines from the above snippet
162    
163     use AnyEvent::MP;
164    
165     sub send_message {
166     my ($msg) = @_;
167    
168     snd $server_port, message => $msg;
169     }
170    
171     ... # the rest of the above script
172    
173 root 1.8 The C<snd> function is exported by L<AnyEvent::MP>, it stands for 'send
174     a message'. The first argument is the I<port> (a I<port> is something
175     that can receive messages, represented by a printable string) of the
176     server which will receive the message. How we get this port will be
177     explained in the next step.
178    
179     The remaining arguments of C<snd> are C<message> and C<$msg>, the first
180     two elements of the I<message> (a I<message> in L<AnyEvent::MP> is a
181     simple list of values, which can be sent to a I<port>).
182    
183     So all the function does is send the two values C<message> (a constant
184     string to tell the server what to expect) and the actual message string.
185    
186     Thats all fine and simple so far, but where do we get the
187     C<$server_port>? Well, we need to get the unique I<port id> of the
188     server's port where it wants to receive all the incoming chat messages. A
189 root 1.9 I<port id> is unfortunately a very unique string, which we are unable to
190     know in advance. But L<AnyEvent::MP> supports the concept of 'registered
191     ports', which is basically a port on the server side registered under
192     a well known name.
193    
194     For example, the server has a port for receiving chat messages with a
195     unique I<port id> and registers it under the name C<chatter>.
196 root 1.4
197 root 1.9 BTW, these "registered port names" should follow similar rules as Perl
198 root 1.4 identifiers, so you should prefix them with your package/module name to
199     make them unique, unless you use them in the main program.
200 elmex 1.1
201 root 1.9 As I<messages> can only be sent to a I<port id> and not just to some name
202     we have to ask the server node for the I<port id> of the port registered
203     as C<chatter>.
204 elmex 1.1
205 root 1.9 =head1 Finding The Chatter Port
206 elmex 1.1
207 root 1.9 Ok, lots of talk, now some code. Now we will actually get the
208     C<$server_port> from the backend:
209 elmex 1.1
210     ...
211    
212     use AnyEvent::MP;
213    
214 root 1.9 my $server_node = "127.0.0.1:1299";
215 elmex 1.1
216 root 1.9 my $client_port = port;
217 elmex 1.1
218 root 1.9 snd $server_node, lookup => "chatter", $client_port, "resolved";
219 elmex 1.1
220 root 1.9 my $resolved_cv = AnyEvent->condvar;
221 elmex 1.1 my $server_port;
222    
223     # setup a receiver callback for the 'resolved' message:
224 root 1.9 rcv $client_port, resolved => sub {
225     my ($tag, $chatter_port_id) = @_;
226 elmex 1.1
227     print "Resolved the server port 'chatter' to $chatter_port_id\n";
228     $server_port = $chatter_port_id;
229    
230     $resolved_cv->send;
231     1
232 root 1.9 };
233 elmex 1.1
234 root 1.9 # lets block the client until we have resolved the server port.
235 elmex 1.1 $resolved_cv->recv;
236    
237     # now setup another receiver callback for the chat messages:
238 root 1.9 rcv $client_port, message => sub {
239     my ($tag, $msg) = @_;
240 elmex 1.1
241     print "chat> $msg\n";
242     0
243 root 1.9 };
244 elmex 1.1
245 root 1.9 # send a 'join' message to the server:
246 elmex 1.1 snd $server_port, join => "$client_port";
247    
248     sub send_message { ...
249    
250 root 1.9 Now that was a lot of new stuff:
251 elmex 1.1
252 root 1.9 First we define the C<$server_node>: In order to refer to another node
253     we need some kind of string to reference it - the node reference. The
254     I<noderef> is basically a comma separated list of C<address:port>
255     pairs. We assume in this tutorial that the server runs on C<127.0.0.1>
256     (localhost) on port 1299, which results in the noderef C<127.0.0.1:1299>.
257    
258     Next, in order to receive a reply from the other node or the server we
259     need to have a I<port> that messages can be sent to. This is what the
260     C<port> function will do for us, it just creates a new local port and
261     returns it's I<port ID> that can then be used to receive messages.
262    
263     When you look carefully, you will see that the first C<snd> uses the
264     C<$server_node> (a noderef) as destination port. Well, what I didn't
265     tell you yet is that each I<node> has a default I<port> to receive
266     messages. The ID of this port is the same as the noderef.
267    
268     This I<default port> provides some special services for us, for example
269     resolving a registered name to a I<port id> (a-ha! finally!).
270    
271     This is exactly what this line does:
272    
273     snd $server_node, lookup => "chatter", $client_port, "resolved";
274    
275     This sends a message with first element being C<lookup>, followed by the
276     (hopefully) registered port name that we want to resolve to a I<port
277     id>: C<chatter>. And in order for the server node to be able to send us
278     back the resolved I<port ID> we have to tell it where to send it: The
279     result message will be sent to C<$client_port> (the I<port id> of the
280     port we just created), and will have the string C<resolved> as the first
281     element.
282    
283     When the node receives this message, it will look up the name, gobble up
284     all the extra arguments we passed, append the resolved name, and send the
285     resulting list as a message.
286 elmex 1.1
287 root 1.9 Next we register a receiver for this C<lookup>-request.
288    
289     rcv $client_port, resolved => sub {
290     my ($tag, $chatter_port_id) = @_;
291     ...
292     1
293     };
294    
295     This sets up a receiver on our own port for messages with the first
296     element being the string C<resolved>. Receivers can match the contents of
297     the messages before actually executing the specified callback.
298    
299     B<Please note> that the every C<rcv> callback has to return either a true
300     or a false value, indicating whether it is B<successful>/B<done> (true) or
301     still wants to B<continue> (false) receiving messages.
302 elmex 1.1
303 root 1.9 In this case we tell the C<$client_port> to look into all the messages
304     it receives and look for the string C<resolved> in the first element of
305     the message. If it is found, the given callback will be called with the
306     message elements as arguments.
307 elmex 1.1
308 root 1.9 Using a string as the first element of the message is called I<tagging>
309     the message. It's common practise to code the 'type' of a message into
310     it's first element, as this allows for simple matching.
311 elmex 1.1
312 root 1.9 The result message will contain the I<port ID> of the well known port
313     C<chatter> as second element, which will be stored in C<$chatter_port_id>.
314 elmex 1.1
315 root 1.9 This port ID will then be stored in C<$server_port>, followed by calling
316     C<send> on $resolved_cv> so the program will continue.
317 elmex 1.1
318 root 1.9 The callback then returns a C<1> (a true value), to indicate that it has
319     done it's job and doesn't want to receive further C<resolved> messages.
320 elmex 1.1
321 root 1.9 After this the chat message receiver callback is registered with the port:
322 elmex 1.1
323 root 1.9 rcv $client_port, message => sub {
324     my ($tag, $msg) = @_;
325 elmex 1.1
326     print "chat> $msg\n";
327 root 1.9
328 elmex 1.1 0
329 root 1.9 };
330 elmex 1.1
331 root 1.9 We assume that all messages that are broadcast to the clients by the
332     server contain the string tag C<message> as first element, and the actual
333     message as second element. The callback returns a false value this time,
334     to indicate that it is not yet done and wants to receive further messages.
335    
336     The last thing to do is to tell the server to send us new chat messages
337     from other clients. We do so by sending the message C<join> followed by
338     our own I<port ID>.
339 elmex 1.1
340     # send the server a 'join' message:
341 root 1.9 snd $server_port, join => $client_port;
342 elmex 1.1
343 root 1.9 This way the server knows where to send all the new messages to.
344 elmex 1.1
345 root 1.8 =head1 The Completed Client
346 elmex 1.1
347     This is the complete client script:
348    
349     #!perl
350 root 1.4
351 elmex 1.1 use AnyEvent;
352     use AnyEvent::Handle;
353     use AnyEvent::MP;
354    
355 root 1.10 my $server_node = "127.0.0.1:1299";
356 elmex 1.1
357 root 1.10 my $client_port = port;
358 elmex 1.1
359 root 1.10 snd $server_node, lookup => "chatter", $client_port, "resolved";
360 elmex 1.1
361 root 1.10 my $resolved_cv = AnyEvent->condvar;
362 elmex 1.1 my $server_port;
363    
364     # setup a receiver callback for the 'resolved' message:
365 root 1.10 rcv $client_port, resolved => sub {
366     my ($tag, $chatter_port_id) = @_;
367 elmex 1.1
368     print "Resolved the server port 'chatter' to $chatter_port_id\n";
369     $server_port = $chatter_port_id;
370    
371     $resolved_cv->send;
372     1
373 root 1.10 };
374 elmex 1.1
375 root 1.10 # lets block the client until we have resolved the server port.
376 elmex 1.1 $resolved_cv->recv;
377    
378     # now setup another receiver callback for the chat messages:
379 root 1.10 rcv $client_port, message => sub {
380     my ($tag, $msg) = @_;
381 elmex 1.1
382     print "chat> $msg\n";
383     0
384 root 1.10 };
385 elmex 1.1
386 root 1.10 # send a 'join' message to the server:
387 elmex 1.1 snd $server_port, join => "$client_port";
388    
389     sub send_message {
390     my ($msg) = @_;
391    
392     snd $server_port, message => $msg;
393     }
394    
395     # make an AnyEvent condition variable for the 'quit' condition
396     # (when we want to exit the client).
397     my $quit_cv = AnyEvent->condvar;
398    
399     my $stdin_hdl = AnyEvent::Handle->new (
400 root 1.10 fh => *STDIN,
401     on_error => sub { $quit_cv->send },
402     on_read => sub {
403 elmex 1.1 my ($hdl) = @_;
404    
405     $hdl->push_read (line => sub {
406     my ($hdl, $line) = @_;
407    
408     if ($line =~ /^\/quit/) { # /quit will end the client
409     $quit_cv->send;
410     } else {
411     send_message ($line);
412     }
413     });
414     }
415     );
416    
417     $quit_cv->recv;
418    
419 root 1.8 =head1 The Server
420 elmex 1.1
421 root 1.10 Ok, we finally come to the server.
422    
423     The server of course also needs to set up a port, and in addition needs to
424     I<register> it, so the clients can find it.
425 elmex 1.1
426 root 1.10 Again, let's jump directly into the code:
427 elmex 1.1
428     #!perl
429 root 1.4
430 elmex 1.1 use AnyEvent;
431     use AnyEvent::MP;
432    
433 root 1.10 become_public "127.0.0.1:1299";
434 elmex 1.1
435 root 1.10 my $chatter_port = port;
436    
437     reg $chatter_port, "chatter";
438 elmex 1.1
439     my %client_ports;
440    
441 root 1.10 rcv $chatter_port,
442     join => sub {
443     my ($tag, $client_port) = @_;
444 elmex 1.1
445 root 1.10 print "got new client port: $client_port\n";
446     $client_ports{$client_port} = 1;
447 elmex 1.5
448 root 1.10 0
449     },
450     message => sub {
451     my ($tag, $msg) = @_;
452 elmex 1.1
453 root 1.10 print "message> $msg\n";
454 elmex 1.1
455 root 1.10 snd $_, message => $msg
456     for keys %client_ports;
457 elmex 1.5
458 root 1.10 0
459 root 1.11 };
460 elmex 1.1
461     AnyEvent->condvar->recv;
462    
463 root 1.10 That is all. Looks much simpler than the client, doesn't it?
464    
465     Let's quickly look over it, as C<rcv> has already been discussed in the
466     client part of this tutorial above.
467 elmex 1.2
468     First this:
469    
470 root 1.10 become_public "127.0.0.1:1299";
471 elmex 1.1
472 root 1.10 This will tell our I<node> to become a I<public> node, which means that it
473     can be contacted via TCP. The first argument should be the I<noderef> the
474     server wants to be reachable at. In this case it's the TCP port 1299 on
475     C<127.0.0.1>.
476    
477     Next we set up two receivers, one for the C<join> messages and another one
478     for the actual messages of type C<messsage>. This is done with a single
479     call to C<rcv>, which allows multiple C<< match => $callback >> pairs.
480    
481     In the C<join> callback we receive the client port, which is simply
482     remembered in the C<%client_ports> hash. In the C<message> callback we
483     just iterate through all known C<%client_ports> and relay the message to
484     them.
485 elmex 1.1
486 root 1.10 That concludes the server.
487 elmex 1.2
488 root 1.8 =head1 The Remaining Problems
489 elmex 1.1
490 root 1.10 The implementation as shown still has some bugs. For instance: How does
491     the server know that the client isn't there anymore, so it can clean up
492     the C<%client_ports> hash? Also, the chat messages have no originator, so
493     we don't know who actually sent the message (which would be quite useful
494 elmex 1.1 for human-to-human interaction: to know who the other one is :).
495    
496 root 1.10 But aside from these issues I hope this tutorial showed you the basics of
497 elmex 1.1 L<AnyEvent::MP> and explained some common idioms.
498    
499 elmex 1.7 How to solve the reliability and C<%client_ports> cleanup problem will
500     be explained later in this tutorial (TODO).
501    
502 root 1.8 =head1 Inside The Protocol
503 elmex 1.7
504     Now, for the interested parties, let me explain some details about the protocol
505     that L<AnyEvent::MP> nodes use to communicate to each other. If you are not
506     interested you can skip this section.
507    
508 root 1.14 Usually TCP is used for communication. Each I<node>, if configured to be
509     a I<public> node with the C<initialise_node> function will listen on the
510     configured TCP port (default is 4040).
511 elmex 1.7
512 root 1.10 If then one I<node> wants to send a message to another I<node> it will
513     connect to the host and port given in the I<port ID>.
514 elmex 1.7
515 root 1.10 Then some handshaking occurs to check whether both I<nodes> know the
516     I<shared secret>. Optionally, TLS can be enabled (about how to do this
517     exactly please consult the L<AnyEvent::MP> man page, just a hint: It
518     should be enough to put the private key and (self signed) certificate in
519     the C<~/.aemp-secret> file of all nodes).
520    
521     After the handshake, messages will be exchanged using a serialiser
522     (usually L<JSON> is used for this, but it is also possible to use other
523     serialization formats such as L<Storable>).
524 elmex 1.7
525 elmex 1.1 =head1 SEE ALSO
526    
527     L<AnyEvent>
528    
529     L<AnyEvent::Handle>
530    
531     L<AnyEvent::MP>
532    
533     =head1 AUTHOR
534    
535     Robin Redeker <elmex@ta-sa.org>
536 root 1.4