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