ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MP/MP/Intro.pod
Revision: 1.4
Committed: Mon Aug 3 09:19:21 2009 UTC (14 years, 9 months ago) by root
Branch: MAIN
Changes since 1.3: +39 -32 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.4 =head1 Message Passing for the Non-Blocked Mind
2 elmex 1.1
3     =head2 Introduction and Terminology
4    
5 root 1.4 This is a tutorial about how to get the swing of the new L<AnyEvent::MP>
6 elmex 1.1 module. Which allows us to transparently pass messages to our own process and
7     to other process on other or the same host.
8    
9 root 1.4 What kind of messages? Well, basically a message here means a list of
10     Perl strings, numbers, hashes and arrays, mostly everything that can be
11     expressed in a L<JSON> text (as JSON is used by default in the protocol).
12 elmex 1.1
13 root 1.4 And next you might ask: between which entities are those messages being "passed"?
14 elmex 1.1 Basically between C<nodes>, which are basically your applications (as in
15     processes) that use L<AnyEvent::MP> that run either on the same or different
16     hosts.
17    
18 root 1.4 In this tutorial I'll show you how to write a simple chat server based on
19 elmex 1.1 L<AnyEvent::MP>.
20    
21     =head2 The Chat Client
22    
23 root 1.4 OK, lets start by implementing the "frontend" of the client. We will delay the
24 elmex 1.1 explanation and the code of the server until we finished the client, as the
25     most complex things actually happen in the client.
26    
27     We will use L<AnyEvent::Handle> to do non-blocking IO read on standard input:
28    
29     #!perl
30     use AnyEvent;
31     use AnyEvent::Handle;
32    
33     sub send_message {
34     die "This is where we will send the messages to the server"
35     . "in the next step of this tutorial.\n"
36     }
37    
38     # make an AnyEvent condition variable for the 'quit' condition
39     # (when we want to exit the client).
40     my $quit_cv = AnyEvent->condvar;
41    
42     my $stdin_hdl = AnyEvent::Handle->new (
43     fh => \*STDIN,
44     on_read => sub {
45     my ($hdl) = @_;
46    
47     $hdl->push_read (line => sub {
48     my ($hdl, $line) = @_;
49    
50     if ($line =~ /^\/quit/) { # /quit will end the client
51     $quit_cv->send;
52    
53     } else {
54     send_message ($line);
55     }
56     });
57     }
58     );
59    
60     $quit_cv->recv;
61    
62 root 1.4 This is now a very basic client. Explaining explicitly what
63     L<AnyEvent::Handle> does or what a I<condvar> is all about is out of scope
64     of this document, please consult L<AnyEvent::Intro> or the manual pages
65     for L<AnyEvent> and L<AnyEvent::Handle>.
66 elmex 1.1
67     =head2 First Step Into Messaging
68    
69     Now we take a look at L<AnyEvent::MP>. We need to know what to do in
70 root 1.4 C<send_message>. This is an example of how it might look like:
71 elmex 1.1
72     ... # the use lines from the above snippet
73    
74     use AnyEvent::MP;
75    
76     sub send_message {
77     my ($msg) = @_;
78    
79     snd $server_port, message => $msg;
80     }
81    
82     ... # the rest of the above script
83    
84     The C<snd> function is exported by L<AnyEvent::MP>, it stands for 'send a
85     message'. The first argument is the I<port> (a I<port> is something that can
86     receive messages) of the server which will receive the message . How we get it
87     will be explained in the next step. The next arguments of C<snd> are
88 root 1.4 C<message> and C<$msg> are the first two elements of the I<message> (a
89 elmex 1.1 I<message> in L<AnyEvent::MP> is a be a simple list of values, which can be
90     sent to I<ports>).
91    
92     Thats all fine so far, but how do we get the C<$server_port>? Well, we will
93     need to get the unique I<port id> of the server's port where he wants to
94     receive all the incoming chat messages. A I<port id> is unfortunately a very
95     unique string, which we are unable to know in advance. But L<AnyEvent::MP>
96     supports the concept of 'well known ports', which is basically a port on the
97     server side registered under a well known name. For example, the server has a
98     port for receiving chat messages with a unique I<port id> and registered it
99 root 1.4 under the name C<chatter>.
100    
101     BTW, these "well known port names" should follow similar rules as Perl
102     identifiers, so you should prefix them with your package/module name to
103     make them unique, unless you use them in the main program.
104 elmex 1.1
105     As I<messages> can only be sent to a I<port id> and not just to a name we have
106     to ask the server I<node> what I<port id> has the well known port with the
107 root 1.4 name C<chatter>.
108 elmex 1.1
109     Another new term, what is a I<node>: The messaging network that can be created with
110     L<AnyEvent::MP> consists of I<nodes>. A I<node> handles all the connection and
111 root 1.4 low level message sending logic for its application. The application in this
112 elmex 1.1 case is the server. Also every client has/is a I<node>.
113    
114     =head2 Getting The Chatter Port
115    
116     Ok, lots of talk, now some code. Now we will actually get the C<$server_port>
117     from the backend:
118    
119     ...
120    
121     use AnyEvent::MP;
122    
123     my $resolved_cv = AnyEvent->condvar;
124    
125     my $client_port = create_port;
126    
127     my $server_node = "localhost:1299#";
128    
129     snd $server_node, wkp => "chatter", "$client_port", "resolved";
130    
131     my $server_port;
132    
133     # setup a receiver callback for the 'resolved' message:
134     $client_port->rcv (resolved => sub {
135     my ($client_port, $type, $chatter_port_id) = @_;
136    
137     print "Resolved the server port 'chatter' to $chatter_port_id\n";
138     $server_port = $chatter_port_id;
139    
140     $resolved_cv->send;
141     1
142     });
143    
144     # lets block the client until we resolved the server port.
145     $resolved_cv->recv;
146    
147     # now setup another receiver callback for the chat messages:
148     $client_port->rcv (message => sub {
149     my ($client_port, $type, $msg) = @_;
150    
151     print "chat> $msg\n";
152     0
153     });
154    
155     # send the server a 'join' message:
156     snd $server_port, join => "$client_port";
157    
158     sub send_message { ...
159    
160     Now that was a lot of new stuff. In order to ask the server and receive an
161     answer we need to have a I<port> where we can receive the answer.
162     This is what C<create_port> will do for us, it just creates a new local
163     port and returns us an object (that will btw. stringify to the I<port id>),
164     that we can use to receive messages.
165    
166     Next thing is the C<$server_node>. In order to refer to another node we need
167     some kind of string to reference it. The I<noderef> is basically a comma
168     seperated list of C<host:port> pairs. We assume in this tutorial that the
169     server runs on your localhost at port 1299, this gives us the noderef
170 root 1.4 C<localhost:1299>.
171 elmex 1.1
172 root 1.4 Now you might ask what the C<#> at the end in C<$server_node> the above
173 elmex 1.1 example is about. Well, what I didn't tell you yet is that each I<node> has a
174     default I<port> to receive messages. The default port is the empty string
175     C<"">. The I<default port> of a I<node> also provides some special services for
176     us, for example resolving a well known port to a I<port id>.
177    
178     Now to this line:
179    
180     snd $server_node, wkp => "chatter", "$client_port", "resolved";
181    
182 root 1.4 We send a message with first element being C<wkp> (standing for 'well known
183 elmex 1.1 port'). Then the well known port name that we want to resolve to a I<port id>:
184 root 1.4 C<chatter>. And in order for the server node to be able to send us back the
185 elmex 1.1 resolved I<port id> we have to tell it where to send the result message: The
186 root 1.4 result message will have as it's first argument the string C<resolved> and
187     will be sent to C<$client_port> (the I<port id> of our own just created
188 elmex 1.1 port).
189    
190 root 1.4 Next comes the receiver for this C<wkp> request.
191 elmex 1.1
192     $client_port->rcv (resolved => sub {
193     my ($client_port, $type, $chatter_port_id) = @_;
194     ...
195     1
196     });
197    
198     This sets up a receiver on our own port for the result message with the first
199 root 1.4 argument being the string C<resolved>. Receivers can match the contents of
200 elmex 1.1 the messages before actually 'sending' it to the given callback.
201    
202     B<Please note> that the given callback has to return either a true or a false
203     value for indicating whether it is B<done> (true value) or still wants to
204     B<continue> (false value) receiving messages.
205    
206     In this case we tell the C<$client_port> to look into the received messages and
207 root 1.4 look for the string C<resolved> in the first element of the message. If it is
208 elmex 1.1 found, the given callback will be called with the C<$client_port> as first
209     argument, and the message as the remaining arguments.
210    
211     We name the first element of the message C<$type> in this case. It's a common
212     idiom to code the 'type' of a message into it's first element, this allows for
213     simple matching.
214    
215 root 1.4 The result message will contain the I<port id> of the well known port C<chatter>
216 elmex 1.1 as next element, and will be put in C<$chatter_port_id>.
217    
218     Next we just assign C<$server_port> and return a 1 (a true value)
219     from the callback. It indicates that we are done and don't want to receive
220 root 1.4 further C<resolved> messages with this callback.
221 elmex 1.1
222     Now we continue to the rest of the client by calling C<send> on
223     C<$resolved_cv>.
224    
225     First new step after this is setting up the chat message receiver callback.
226    
227     $client_port->rcv (message => sub {
228     my ($client_port, $type, $msg) = @_;
229    
230     print "chat> $msg\n";
231     0
232     });
233    
234 root 1.4 We assume that all messages that are broadcast to all clients by the server
235     will contain the string C<message> as first element, and the actual message as
236 elmex 1.1 second element. The callback returns a false value this time, to indicate that
237     it wants to continue receiving messages.
238    
239     Last but not least we actually tell the server to send us
240     the new chat messages from other clients. We do so by sending the
241 root 1.4 message type C<join> followed by our own I<port id>.
242 elmex 1.1
243     # send the server a 'join' message:
244     snd $server_port, join => "$client_port";
245    
246     Then the server knows where to send all the new messages to.
247    
248     =head2 The Finished Client
249    
250     This is the complete client script:
251    
252     #!perl
253 root 1.4
254 elmex 1.1 use AnyEvent;
255     use AnyEvent::Handle;
256     use AnyEvent::MP;
257    
258     my $resolved_cv = AnyEvent->condvar;
259    
260     my $client_port = create_port;
261    
262     my $server_node = "localhost:1299#";
263    
264     snd $server_node, wkp => "chatter", "$client_port", "resolved";
265    
266     my $server_port;
267    
268     # setup a receiver callback for the 'resolved' message:
269     $client_port->rcv (resolved => sub {
270     my ($client_port, $type, $chatter_port_id) = @_;
271    
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     });
278    
279     # lets block the client until we resolved the server port.
280     $resolved_cv->recv;
281    
282     # now setup another receiver callback for the chat messages:
283     $client_port->rcv (message => sub {
284     my ($client_port, $type, $msg) = @_;
285    
286     print "chat> $msg\n";
287     0
288     });
289    
290     # send the server a 'join' message:
291     snd $server_port, join => "$client_port";
292    
293     sub send_message {
294     my ($msg) = @_;
295    
296     snd $server_port, message => $msg;
297     }
298    
299     # make an AnyEvent condition variable for the 'quit' condition
300     # (when we want to exit the client).
301     my $quit_cv = AnyEvent->condvar;
302    
303     my $stdin_hdl = AnyEvent::Handle->new (
304     fh => \*STDIN,
305     on_read => sub {
306     my ($hdl) = @_;
307    
308     $hdl->push_read (line => sub {
309     my ($hdl, $line) = @_;
310    
311     if ($line =~ /^\/quit/) { # /quit will end the client
312     $quit_cv->send;
313    
314     } else {
315     send_message ($line);
316     }
317     });
318     }
319     );
320    
321     $quit_cv->recv;
322    
323     =head2 The Server
324    
325     Ok, now finally to the server. What do we need? Well, we need to setup
326 root 1.4 the well known port C<chatter> where all clients send their messages to.
327 elmex 1.1
328     Up and into code right now:
329    
330     #!perl
331 root 1.4
332 elmex 1.1 use AnyEvent;
333     use AnyEvent::MP;
334    
335     become_public "localhost:1299";
336    
337     my $chatter_port = create_port;
338     $chatter_port->register ("chatter");
339    
340     my %client_ports;
341    
342     $chatter_port->rcv (join => sub {
343     my ($chatter_port, $type, $client_port) = @_;
344    
345     $client_ports{$client_port} = 1;
346     0
347     });
348    
349     $chatter_port->rcv (message => sub {
350     my ($chatter_port, $type, $msg) = @_;
351    
352     snd $_, message => $msg for keys %client_ports;
353     0
354     });
355    
356     AnyEvent->condvar->recv;
357    
358     This is all. Looks much easier, doesn't it? I'll explain it only shortly, as
359     we had the discussion of the C<rcv> method in the client part of this tutorial
360 elmex 1.2 above.
361    
362     First this:
363    
364     become_public "localhost:1299";
365    
366     This will tell our I<node> to become a I<public> node, which means that it can
367     be contacted via TCP. The first argument should be the I<noderef> the server
368     wants to be reachable at. In this case it's the TCP port 1299 on localhost.
369    
370     Next we bascially setup two receivers, one for the C<join> messages and
371 elmex 1.1 another one for the actual messages of type C<messsage>.
372    
373     In the C<join> message we get the client's port, which we just remember in the
374     C<%client_ports> hash. In the receiver for the message type C<message> we will
375     just iterate through all known C<%client_ports> and relay the message to them.
376    
377 elmex 1.2 And thats it.
378    
379 elmex 1.1 =head2 The Remaining Problems
380    
381     The shown implementation still has some bugs. For instance: How does the
382     server know that the client isn't there anymore, and can cleanup the
383     C<%client_ports> hash? And also the chat messages have no originator,
384     so we don't know who actually sent the message (which would be quite useful
385     for human-to-human interaction: to know who the other one is :).
386    
387     But aside from these issues I hope this tutorial got you the swing of
388     L<AnyEvent::MP> and explained some common idioms.
389    
390     =head1 SEE ALSO
391    
392     L<AnyEvent>
393    
394     L<AnyEvent::Handle>
395    
396     L<AnyEvent::MP>
397    
398     =head1 AUTHOR
399    
400     Robin Redeker <elmex@ta-sa.org>
401 root 1.4