aboutsummaryrefslogtreecommitdiffstats
path: root/system/doc/getting_started/conc_prog.xml
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /system/doc/getting_started/conc_prog.xml
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'system/doc/getting_started/conc_prog.xml')
-rw-r--r--system/doc/getting_started/conc_prog.xml894
1 files changed, 894 insertions, 0 deletions
diff --git a/system/doc/getting_started/conc_prog.xml b/system/doc/getting_started/conc_prog.xml
new file mode 100644
index 0000000000..34ae428b2c
--- /dev/null
+++ b/system/doc/getting_started/conc_prog.xml
@@ -0,0 +1,894 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2003</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>Concurrent Programming</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>conc_prog.xml</file>
+ </header>
+
+ <section>
+ <title>Processes</title>
+ <p>One of the main reasons for using Erlang instead of other
+ functional languages is Erlang's ability to handle concurrency
+ and distributed programming. By concurrency we mean programs
+ which can handle several threads of execution at the same time.
+ For example, modern operating systems would allow you to use a
+ word processor, a spreadsheet, a mail client and a print job all
+ running at the same time. Of course each processor (CPU) in
+ the system is probably only handling one thread (or job) at a
+ time, but it swaps between the jobs a such a rate that it gives
+ the illusion of running them all at the same time. It is easy to
+ create parallel threads of execution in an Erlang program and it
+ is easy to allow these threads to communicate with each other. In
+ Erlang we call each thread of execution a <em>process</em>.</p>
+ <p>(Aside: the term "process" is usually used when the threads of
+ execution share no data with each other and the term "thread"
+ when they share data in some way. Threads of execution in Erlang
+ share no data, that's why we call them processes).</p>
+ <p>The Erlang BIF <c>spawn</c> is used to create a new process:
+ <c>spawn(Module, Exported_Function, List of Arguments)</c>.
+ Consider the following module:</p>
+ <code type="none">
+-module(tut14).
+
+-export([start/0, say_something/2]).
+
+say_something(What, 0) ->
+ done;
+say_something(What, Times) ->
+ io:format("~p~n", [What]),
+ say_something(What, Times - 1).
+
+start() ->
+ spawn(tut14, say_something, [hello, 3]),
+ spawn(tut14, say_something, [goodbye, 3]).</code>
+ <pre>
+5> <input>c(tut14).</input>
+{ok,tut14}
+6> <input>tut14:say_something(hello, 3).</input>
+hello
+hello
+hello
+done</pre>
+ <p>We can see that function <c>say_something</c> writes its first
+ argument the number of times specified by second argument. Now
+ look at the function <c>start</c>. It starts two Erlang processes,
+ one which writes "hello" three times and one which writes
+ "goodbye" three times. Both of these processes use the function
+ <c>say_something</c>. Note that a function used in this way by
+ <c>spawn</c> to start a process must be exported from the module
+ (i.e. in the <c>-export</c> at the start of the module).</p>
+ <pre>
+9> <input>tut14:start().</input>
+hello
+goodbye
+&lt;0.63.0>
+hello
+goodbye
+hello
+goodbye</pre>
+ <p>Notice that it didn't write "hello" three times and then
+ "goodbye" three times, but the first process wrote a "hello",
+ the second a "goodbye", the first another "hello" and so forth.
+ But where did the &lt;0.63.0&gt; come from? The return value of a
+ function is of course the return value of the last "thing" in
+ the function. The last thing in the function <c>start</c> is</p>
+ <code type="none">
+spawn(tut14, say_something, [goodbye, 3]).</code>
+ <p><c>spawn</c> returns a <em>process identifier</em>, or
+ <em>pid</em>, which uniquely identifies the process. So &lt;0.63.0&gt;
+ is the pid of the <c>spawn</c> function call above. We will see
+ how to use pids in the next example.</p>
+ <p>Note as well that we have used ~p instead of ~w in
+ <c>io:format</c>. To quote the manual: "~p Writes the data with
+ standard syntax in the same way as ~w, but breaks terms whose
+ printed representation is longer than one line into many lines
+ and indents each line sensibly. It also tries to detect lists of
+ printable characters and to output these as strings".</p>
+ </section>
+
+ <section>
+ <title>Message Passing</title>
+ <p>In the following example we create two processes which send
+ messages to each other a number of times.</p>
+ <code type="none">
+-module(tut15).
+
+-export([start/0, ping/2, pong/0]).
+
+ping(0, Pong_PID) ->
+ Pong_PID ! finished,
+ io:format("ping finished~n", []);
+
+ping(N, Pong_PID) ->
+ Pong_PID ! {ping, self()},
+ receive
+ pong ->
+ io:format("Ping received pong~n", [])
+ end,
+ ping(N - 1, Pong_PID).
+
+pong() ->
+ receive
+ finished ->
+ io:format("Pong finished~n", []);
+ {ping, Ping_PID} ->
+ io:format("Pong received ping~n", []),
+ Ping_PID ! pong,
+ pong()
+ end.
+
+start() ->
+ Pong_PID = spawn(tut15, pong, []),
+ spawn(tut15, ping, [3, Pong_PID]).</code>
+ <pre>
+1> <input>c(tut15).</input>
+{ok,tut15}
+2> <input>tut15: start().</input>
+&lt;0.36.0>
+Pong received ping
+Ping received pong
+Pong received ping
+Ping received pong
+Pong received ping
+Ping received pong
+ping finished
+Pong finished</pre>
+ <p>The function <c>start</c> first creates a process, let's call it
+ "pong":</p>
+ <code type="none">
+Pong_PID = spawn(tut15, pong, [])</code>
+ <p>This process executes <c>tut15:pong()</c>. <c>Pong_PID</c> is
+ the process identity of the "pong" process. The function
+ <c>start</c> now creates another process "ping".</p>
+ <code type="none">
+spawn(tut15, ping, [3, Pong_PID]),</code>
+ <p>this process executes</p>
+ <code type="none">
+tut15:ping(3, Pong_PID)</code>
+ <p>&lt;0.36.0&gt; is the return value from the <c>start</c> function.</p>
+ <p>The process "pong" now does:</p>
+ <code type="none">
+receive
+ finished ->
+ io:format("Pong finished~n", []);
+ {ping, Ping_PID} ->
+ io:format("Pong received ping~n", []),
+ Ping_PID ! pong,
+ pong()
+end.</code>
+ <p>The <c>receive</c> construct is used to allow processes to wait
+ for messages from other processes. It has the format:</p>
+ <code type="none">
+receive
+ pattern1 ->
+ actions1;
+ pattern2 ->
+ actions2;
+ ....
+ patternN
+ actionsN
+end.</code>
+ <p>Note: no ";" before the <c>end</c>.</p>
+ <p>Messages between Erlang processes are simply valid Erlang terms.
+ I.e. they can be lists, tuples, integers, atoms, pids etc.</p>
+ <p>Each process has its own input queue for messages it receives.
+ New messages received are put at the end of the queue. When a
+ process executes a <c>receive</c>, the first message in the queue
+ is matched against the first pattern in the <c>receive</c>, if
+ this matches, the message is removed from the queue and
+ the actions corresponding to the the pattern are executed.</p>
+ <p>However, if the first pattern does not match, the second pattern
+ is tested, if this matches the message is removed from the queue
+ and the actions corresponding to the second pattern are executed.
+ If the second pattern does not match the third is tried and so on
+ until there are no more pattern to test. If there are no more
+ patterns to test, the first message is kept in the queue and we
+ try the second message instead. If this matches any pattern,
+ the appropriate actions are executed and the second message is
+ removed from the queue (keeping the first message and any other
+ messages in the queue). If the second message does not match we
+ try the third message and so on until we reach the end of
+ the queue. If we reach the end of the queue, the process blocks
+ (stops execution) and waits until a new message is received and
+ this procedure is repeated.</p>
+ <p>Of course the Erlang implementation is "clever" and minimizes
+ the number of times each message is tested against the patterns
+ in each <c>receive</c>.</p>
+ <p>Now back to the ping pong example.</p>
+ <p>"Pong" is waiting for messages. If the atom <c>finished</c> is
+ received, "pong" writes "Pong finished" to the output and as it
+ has nothing more to do, terminates. If it receives a message with
+ the format:</p>
+ <code type="none">
+{ping, Ping_PID}</code>
+ <p>it writes "Pong received ping" to the output and sends the atom
+ <c>pong</c> to the process "ping":</p>
+ <code type="none">
+Ping_PID ! pong</code>
+ <p>Note how the operator "!" is used to send messages. The syntax
+ of "!" is:</p>
+ <code type="none">
+Pid ! Message</code>
+ <p>I.e. <c>Message</c> (any Erlang term) is sent to the process
+ with identity <c>Pid</c>.</p>
+ <p>After sending the message <c>pong</c>, to the process "ping",
+ "pong" calls the <c>pong</c> function again, which causes it to
+ get back to the <c>receive</c> again and wait for another message.
+ Now let's look at the process "ping". Recall that it was started
+ by executing:</p>
+ <code type="none">
+tut15:ping(3, Pong_PID)</code>
+ <p>Looking at the function <c>ping/2</c> we see that the second
+ clause of <c>ping/2</c> is executed since the value of the first
+ argument is 3 (not 0) (first clause head is
+ <c>ping(0,Pong_PID)</c>, second clause head is
+ <c>ping(N,Pong_PID)</c>, so <c>N</c> becomes 3).</p>
+ <p>The second clause sends a message to "pong":</p>
+ <code type="none">
+Pong_PID ! {ping, self()},</code>
+ <p><c>self()</c> returns the pid of the process which executes
+ <c>self()</c>, in this case the pid of "ping". (Recall the code
+ for "pong", this will land up in the variable <c>Ping_PID</c> in
+ the <c>receive</c> previously explained).</p>
+ <p>"Ping" now waits for a reply from "pong":</p>
+ <code type="none">
+receive
+ pong ->
+ io:format("Ping received pong~n", [])
+end,</code>
+ <p>and writes "Ping received pong" when this reply arrives, after
+ which "ping" calls the <c>ping</c> function again.</p>
+ <code type="none">
+ping(N - 1, Pong_PID)</code>
+ <p><c>N-1</c> causes the first argument to be decremented until it
+ becomes 0. When this occurs, the first clause of <c>ping/2</c>
+ will be executed:</p>
+ <code type="none">
+ping(0, Pong_PID) ->
+ Pong_PID ! finished,
+ io:format("ping finished~n", []);</code>
+ <p>The atom <c>finished</c> is sent to "pong" (causing it to
+ terminate as described above) and "ping finished" is written to
+ the output. "Ping" then itself terminates as it has nothing left
+ to do.</p>
+ </section>
+
+ <section>
+ <title>Registered Process Names</title>
+ <p>In the above example, we first created "pong" so as to be able
+ to give the identity of "pong" when we started "ping". I.e. in
+ some way "ping" must be able to know the identity of "pong" in
+ order to be able to send a message to it. Sometimes processes
+ which need to know each others identities are started completely
+ independently of each other. Erlang thus provides a mechanism for
+ processes to be given names so that these names can be used as
+ identities instead of pids. This is done by using
+ the <c>register</c> BIF:</p>
+ <code type="none">
+register(some_atom, Pid)</code>
+ <p>We will now re-write the ping pong example using this and giving
+ the name <c>pong</c> to the "pong" process:</p>
+ <code type="none">
+-module(tut16).
+
+-export([start/0, ping/1, pong/0]).
+
+ping(0) ->
+ pong ! finished,
+ io:format("ping finished~n", []);
+
+ping(N) ->
+ pong ! {ping, self()},
+ receive
+ pong ->
+ io:format("Ping received pong~n", [])
+ end,
+ ping(N - 1).
+
+pong() ->
+ receive
+ finished ->
+ io:format("Pong finished~n", []);
+ {ping, Ping_PID} ->
+ io:format("Pong received ping~n", []),
+ Ping_PID ! pong,
+ pong()
+ end.
+
+start() ->
+ register(pong, spawn(tut16, pong, [])),
+ spawn(tut16, ping, [3]).</code>
+ <pre>
+2> <input>c(tut16).</input>
+{ok, tut16}
+3> <input>tut16:start().</input>
+&lt;0.38.0>
+Pong received ping
+Ping received pong
+Pong received ping
+Ping received pong
+Pong received ping
+Ping received pong
+ping finished
+Pong finished</pre>
+ <p>In the <c>start/0</c> function,</p>
+ <code type="none">
+register(pong, spawn(tut16, pong, [])),</code>
+ <p>both spawns the "pong" process and gives it the name <c>pong</c>.
+ In the "ping" process we can now send messages to <c>pong</c> by:</p>
+ <code type="none">
+pong ! {ping, self()},</code>
+ <p>so that <c>ping/2</c> now becomes <c>ping/1</c> as we don't have
+ to use the argument <c>Pong_PID</c>.</p>
+ </section>
+
+ <section>
+ <title>Distributed Programming</title>
+ <p>Now let's re-write the ping pong program with "ping" and "pong"
+ on different computers. Before we do this, there are a few things
+ we need to set up to get this to work. The distributed Erlang
+ implementation provides a basic security mechanism to prevent
+ unauthorized access to an Erlang system on another computer
+ (*manual*). Erlang systems which talk to each other must have
+ the same <em>magic cookie</em>. The easiest way to achieve this
+ is by having a file called <c>.erlang.cookie</c> in your home
+ directory on all machines which on which you are going to run
+ Erlang systems communicating with each other (on Windows systems
+ the home directory is the directory where pointed to by the $HOME
+ environment variable - you may need to set this. On Linux or Unix
+ you can safely ignore this and simply create a file called
+ <c>.erlang.cookie</c> in the directory you get to after executing
+ the command <c>cd</c> without any argument).
+ The <c>.erlang.cookie</c> file should contain on line with
+ the same atom. For example on Linux or Unix in the OS shell:</p>
+ <pre>
+$ <input>cd</input>
+$ <input>cat > .erlang.cookie</input>
+this_is_very_secret
+$ <input>chmod 400 .erlang.cookie</input></pre>
+ <p>The <c>chmod</c> above make the <c>.erlang.cookie</c> file
+ accessible only by the owner of the file. This is a requirement.</p>
+ <p>When you start an Erlang system which is going to talk to other
+ Erlang systems, you must give it a name, eg: </p>
+ <pre>
+$ <input>erl -sname my_name</input></pre>
+ <p>We will see more details of this later (*manual*). If you want to
+ experiment with distributed Erlang, but you only have one
+ computer to work on, you can start two separate Erlang systems on
+ the same computer but give them different names. Each Erlang
+ system running on a computer is called an Erlang node.</p>
+ <p>(Note: <c>erl -sname</c> assumes that all nodes are in the same
+ IP domain and we can use only the first component of the IP
+ address, if we want to use nodes in different domains we use
+ <c>-name</c> instead, but then all IP address must be given in
+ full (*manual*).</p>
+ <p>Here is the ping pong example modified to run on two separate
+ nodes:</p>
+ <code type="none">
+-module(tut17).
+
+-export([start_ping/1, start_pong/0, ping/2, pong/0]).
+
+ping(0, Pong_Node) ->
+ {pong, Pong_Node} ! finished,
+ io:format("ping finished~n", []);
+
+ping(N, Pong_Node) ->
+ {pong, Pong_Node} ! {ping, self()},
+ receive
+ pong ->
+ io:format("Ping received pong~n", [])
+ end,
+ ping(N - 1, Pong_Node).
+
+pong() ->
+ receive
+ finished ->
+ io:format("Pong finished~n", []);
+ {ping, Ping_PID} ->
+ io:format("Pong received ping~n", []),
+ Ping_PID ! pong,
+ pong()
+ end.
+
+start_pong() ->
+ register(pong, spawn(tut17, pong, [])).
+
+start_ping(Pong_Node) ->
+ spawn(tut17, ping, [3, Pong_Node]).</code>
+ <p>Let us assume we have two computers called gollum and kosken. We
+ will start a node on kosken called ping and then a node on gollum
+ called pong.</p>
+ <p>On kosken (on a Linux/Unix system):</p>
+ <pre>
+kosken> <input>erl -sname ping</input>
+Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
+
+Eshell V5.2.3.7 (abort with ^G)
+(ping@kosken)1></pre>
+ <p>On gollum:</p>
+ <pre>
+gollum> <input>erl -sname pong</input>
+Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
+
+Eshell V5.2.3.7 (abort with ^G)
+(pong@gollum)1></pre>
+ <p>Now we start the "pong" process on gollum:</p>
+ <pre>
+(pong@gollum)1> <input>tut17:start_pong().</input>
+true</pre>
+ <p>and start the "ping" process on kosken (from the code above you
+ will see that a parameter of the <c>start_ping</c> function is
+ the node name of the Erlang system where "pong" is running):</p>
+ <pre>
+(ping@kosken)1> <input>tut17:start_ping(pong@gollum).</input>
+&lt;0.37.0>
+Ping received pong
+Ping received pong
+Ping received pong
+ping finished</pre>
+ <p>Here we see that the ping pong program has run, on the "pong"
+ side we see:</p>
+ <pre>
+(pong@gollum)2>
+Pong received ping
+Pong received ping
+Pong received ping
+Pong finished
+(pong@gollum)2></pre>
+ <p>Looking at the <c>tut17</c> code we see that the <c>pong</c>
+ function itself is unchanged, the lines:</p>
+ <code type="none">
+{ping, Ping_PID} ->
+ io:format("Pong received ping~n", []),
+ Ping_PID ! pong,</code>
+ <p>work in the same way irrespective of on which node the "ping"
+ process is executing. Thus Erlang pids contain information about
+ where the process executes so if you know the pid of a process,
+ the "!" operator can be used to send it a message if the process
+ is on the same node or on a different node.</p>
+ <p>A difference is how we send messages to a registered process on
+ another node:</p>
+ <code type="none">
+{pong, Pong_Node} ! {ping, self()},</code>
+ <p>We use a tuple <c>{registered_name,node_name}</c> instead of
+ just the <c>registered_name</c>.</p>
+ <p>In the previous example, we started "ping" and "pong" from
+ the shells of two separate Erlang nodes. <c>spawn</c> can also be
+ used to start processes in other nodes. The next example is
+ the ping pong program, yet again, but this time we will start
+ "ping" in another node:</p>
+ <code type="none">
+-module(tut18).
+
+-export([start/1, ping/2, pong/0]).
+
+ping(0, Pong_Node) ->
+ {pong, Pong_Node} ! finished,
+ io:format("ping finished~n", []);
+
+ping(N, Pong_Node) ->
+ {pong, Pong_Node} ! {ping, self()},
+ receive
+ pong ->
+ io:format("Ping received pong~n", [])
+ end,
+ ping(N - 1, Pong_Node).
+
+pong() ->
+ receive
+ finished ->
+ io:format("Pong finished~n", []);
+ {ping, Ping_PID} ->
+ io:format("Pong received ping~n", []),
+ Ping_PID ! pong,
+ pong()
+ end.
+
+start(Ping_Node) ->
+ register(pong, spawn(tut18, pong, [])),
+ spawn(Ping_Node, tut18, ping, [3, node()]).</code>
+ <p>Assuming an Erlang system called ping (but not the "ping"
+ process) has already been started on kosken, then on gollum we do:</p>
+ <pre>
+(pong@gollum)1> <input>tut18:start(ping@kosken).</input>
+&lt;3934.39.0>
+Pong received ping
+Ping received pong
+Pong received ping
+Ping received pong
+Pong received ping
+Ping received pong
+Pong finished
+ping finished</pre>
+ <p>Notice we get all the output on gollum. This is because the io
+ system finds out where the process is spawned from and sends all
+ output there.</p>
+ </section>
+
+ <section>
+ <title>A Larger Example</title>
+ <p>Now for a larger example. We will make an extremely simple
+ "messenger". The messenger is a program which allows users to log
+ in on different nodes and send simple messages to each other.</p>
+ <p>Before we start, let's note the following:</p>
+ <list type="bulleted">
+ <item>
+ <p>This example will just show the message passing logic no
+ attempt at all has been made to provide a nice graphical user
+ interface - this can of course also be done in Erlang - but
+ that's another tutorial.</p>
+ </item>
+ <item>
+ <p>This sort of problem can be solved more easily if you use
+ the facilities in OTP, which will also provide methods for
+ updating code on the fly etc. But again, that's another
+ tutorial.</p>
+ </item>
+ <item>
+ <p>The first program we write will contain some inadequacies as
+ regards handling of nodes which disappear, we will correct
+ these in a later version of the program.</p>
+ </item>
+ </list>
+ <p>We will set up the messenger by allowing "clients" to connect to
+ a central server and say who and where they are. I.e. a user
+ won't need to know the name of the Erlang node where another user
+ is located to send a message.</p>
+ <p>File <c>messenger.erl</c>:</p>
+ <marker id="ex"></marker>
+ <code type="none">
+%%% Message passing utility.
+%%% User interface:
+%%% logon(Name)
+%%% One user at a time can log in from each Erlang node in the
+%%% system messenger: and choose a suitable Name. If the Name
+%%% is already logged in at another node or if someone else is
+%%% already logged in at the same node, login will be rejected
+%%% with a suitable error message.
+%%% logoff()
+%%% Logs off anybody at at node
+%%% message(ToName, Message)
+%%% sends Message to ToName. Error messages if the user of this
+%%% function is not logged on or if ToName is not logged on at
+%%% any node.
+%%%
+%%% One node in the network of Erlang nodes runs a server which maintains
+%%% data about the logged on users. The server is registered as "messenger"
+%%% Each node where there is a user logged on runs a client process registered
+%%% as "mess_client"
+%%%
+%%% Protocol between the client processes and the server
+%%% ----------------------------------------------------
+%%%
+%%% To server: {ClientPid, logon, UserName}
+%%% Reply {messenger, stop, user_exists_at_other_node} stops the client
+%%% Reply {messenger, logged_on} logon was successful
+%%%
+%%% To server: {ClientPid, logoff}
+%%% Reply: {messenger, logged_off}
+%%%
+%%% To server: {ClientPid, logoff}
+%%% Reply: no reply
+%%%
+%%% To server: {ClientPid, message_to, ToName, Message} send a message
+%%% Reply: {messenger, stop, you_are_not_logged_on} stops the client
+%%% Reply: {messenger, receiver_not_found} no user with this name logged on
+%%% Reply: {messenger, sent} Message has been sent (but no guarantee)
+%%%
+%%% To client: {message_from, Name, Message},
+%%%
+%%% Protocol between the "commands" and the client
+%%% ----------------------------------------------
+%%%
+%%% Started: messenger:client(Server_Node, Name)
+%%% To client: logoff
+%%% To client: {message_to, ToName, Message}
+%%%
+%%% Configuration: change the server_node() function to return the
+%%% name of the node where the messenger server runs
+
+-module(messenger).
+-export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).
+
+%%% Change the function below to return the name of the node where the
+%%% messenger server runs
+server_node() ->
+ messenger@bill.
+
+%%% This is the server process for the "messenger"
+%%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
+server(User_List) ->
+ receive
+ {From, logon, Name} ->
+ New_User_List = server_logon(From, Name, User_List),
+ server(New_User_List);
+ {From, logoff} ->
+ New_User_List = server_logoff(From, User_List),
+ server(New_User_List);
+ {From, message_to, To, Message} ->
+ server_transfer(From, To, Message, User_List),
+ io:format("list is now: ~p~n", [User_List]),
+ server(User_List)
+ end.
+
+%%% Start the server
+start_server() ->
+ register(messenger, spawn(messenger, server, [[]])).
+
+
+%%% Server adds a new user to the user list
+server_logon(From, Name, User_List) ->
+ %% check if logged on anywhere else
+ case lists:keymember(Name, 2, User_List) of
+ true ->
+ From ! {messenger, stop, user_exists_at_other_node}, %reject logon
+ User_List;
+ false ->
+ From ! {messenger, logged_on},
+ [{From, Name} | User_List] %add user to the list
+ end.
+
+%%% Server deletes a user from the user list
+server_logoff(From, User_List) ->
+ lists:keydelete(From, 1, User_List).
+
+
+%%% Server transfers a message between user
+server_transfer(From, To, Message, User_List) ->
+ %% check that the user is logged on and who he is
+ case lists:keysearch(From, 1, User_List) of
+ false ->
+ From ! {messenger, stop, you_are_not_logged_on};
+ {value, {From, Name}} ->
+ server_transfer(From, Name, To, Message, User_List)
+ end.
+%%% If the user exists, send the message
+server_transfer(From, Name, To, Message, User_List) ->
+ %% Find the receiver and send the message
+ case lists:keysearch(To, 2, User_List) of
+ false ->
+ From ! {messenger, receiver_not_found};
+ {value, {ToPid, To}} ->
+ ToPid ! {message_from, Name, Message},
+ From ! {messenger, sent}
+ end.
+
+
+%%% User Commands
+logon(Name) ->
+ case whereis(mess_client) of
+ undefined ->
+ register(mess_client,
+ spawn(messenger, client, [server_node(), Name]));
+ _ -> already_logged_on
+ end.
+
+logoff() ->
+ mess_client ! logoff.
+
+message(ToName, Message) ->
+ case whereis(mess_client) of % Test if the client is running
+ undefined ->
+ not_logged_on;
+ _ -> mess_client ! {message_to, ToName, Message},
+ ok
+end.
+
+
+%%% The client process which runs on each server node
+client(Server_Node, Name) ->
+ {messenger, Server_Node} ! {self(), logon, Name},
+ await_result(),
+ client(Server_Node).
+
+client(Server_Node) ->
+ receive
+ logoff ->
+ {messenger, Server_Node} ! {self(), logoff},
+ exit(normal);
+ {message_to, ToName, Message} ->
+ {messenger, Server_Node} ! {self(), message_to, ToName, Message},
+ await_result();
+ {message_from, FromName, Message} ->
+ io:format("Message from ~p: ~p~n", [FromName, Message])
+ end,
+ client(Server_Node).
+
+%%% wait for a response from the server
+await_result() ->
+ receive
+ {messenger, stop, Why} -> % Stop the client
+ io:format("~p~n", [Why]),
+ exit(normal);
+ {messenger, What} -> % Normal response
+ io:format("~p~n", [What])
+ end.</code>
+ <p>To use this program you need to:</p>
+ <list type="bulleted">
+ <item>configure the <c>server_node()</c> function</item>
+ <item>copy the compiled code (<c>messenger.beam</c>) to
+ the directory on each computer where you start Erlang.</item>
+ </list>
+ <p>In the following example of use of this program, I have started
+ nodes on four different computers, but if you don't have that
+ many machines available on your network, you could start up
+ several nodes on the same machine.</p>
+ <p>We start up four Erlang nodes, messenger@super, c1@bilbo,
+ c2@kosken, c3@gollum.</p>
+ <p>First we start up a the server at messenger@super:</p>
+ <pre>
+(messenger@super)1> <input>messenger:start_server().</input>
+true</pre>
+ <p>Now Peter logs on at c1@bilbo:</p>
+ <pre>
+(c1@bilbo)1> <input>messenger:logon(peter).</input>
+true
+logged_on</pre>
+ <p>James logs on at c2@kosken:</p>
+ <pre>
+(c2@kosken)1> <input>messenger:logon(james).</input>
+true
+logged_on</pre>
+ <p>and Fred logs on at c3@gollum:</p>
+ <pre>
+(c3@gollum)1> <input>messenger:logon(fred).</input>
+true
+logged_on</pre>
+ <p>Now Peter sends Fred a message:</p>
+ <pre>
+(c1@bilbo)2> <input>messenger:message(fred, "hello").</input>
+ok
+sent</pre>
+ <p>And Fred receives the message and sends a message to Peter and
+ logs off:</p>
+ <pre>
+Message from peter: "hello"
+(c3@gollum)2> <input>messenger:message(peter, "go away, I'm busy").</input>
+ok
+sent
+(c3@gollum)3> <input>messenger:logoff().</input>
+logoff</pre>
+ <p>James now tries to send a message to Fred:</p>
+ <pre>
+(c2@kosken)2> <input>messenger:message(fred, "peter doesn't like you").</input>
+ok
+receiver_not_found</pre>
+ <p>But this fails as Fred has already logged off.</p>
+ <p>First let's look at some of the new concepts we have introduced.</p>
+ <p>There are two versions of the <c>server_transfer</c> function,
+ one with four arguments (<c>server_transfer/4</c>) and one with
+ five (<c>server_transfer/5</c>). These are regarded by Erlang as
+ two separate functions.</p>
+ <p>Note how we write the <c>server</c> function so that it calls
+ itself, <c>server(User_List)</c> and thus creates a loop.
+ The Erlang compiler is "clever" and optimizes the code so that
+ this really is a sort of loop and not a proper function call. But
+ this only works if there is no code after the call, otherwise
+ the compiler will expect the call to return and make a proper
+ function call. This would result in the process getting bigger
+ and bigger for every loop.</p>
+ <p>We use functions in the <c>lists</c> module. This is a very
+ useful module and a study of the manual page is recommended
+ (<c>erl -man lists</c>).
+ <c>lists:keymember(Key,Position,Lists)</c> looks through a list
+ of tuples and looks at <c>Position</c> in each tuple to see if it
+ is the same as <c>Key</c>. The first element is position 1. If it
+ finds a tuple where the element at <c>Position</c> is the same as
+ Key, it returns <c>true</c>, otherwise <c>false</c>.</p>
+ <pre>
+3> <input>lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).</input>
+true
+4> <input>lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).</input>
+false</pre>
+ <p><c>lists:keydelete</c> works in the same way but deletes
+ the first tuple found (if any) and returns the remaining list:</p>
+ <pre>
+5> <input>lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).</input>
+[{x,y,z},{b,b,b},{q,r,s}]</pre>
+ <p><c>lists:keysearch</c> is like <c>lists:keymember</c>, but it
+ returns <c>{value,Tuple_Found}</c> or the atom <c>false</c>.</p>
+ <p>There are a lot more very useful functions in the <c>lists</c>
+ module.</p>
+ <p>An Erlang process will (conceptually) run until it does a
+ <c>receive</c> and there is no message which it wants to receive
+ in the message queue. I say "conceptually" because the Erlang
+ system shares the CPU time between the active processes in
+ the system.</p>
+ <p>A process terminates when there is nothing more for it to do,
+ i.e. the last function it calls simply returns and doesn't call
+ another function. Another way for a process to terminate is for
+ it to call <c>exit/1</c>. The argument to <c>exit/1</c> has a
+ special meaning which we will look at later. In this example we
+ will do <c>exit(normal)</c> which has the same effect as a
+ process running out of functions to call.</p>
+ <p>The BIF <c>whereis(RegisteredName)</c> checks if a registered
+ process of name <c>RegisteredName</c> exists and return the pid
+ of the process if it does exist or the atom <c>undefined</c> if
+ it does not.</p>
+ <p>You should by now be able to understand most of the code above
+ so I'll just go through one case: a message is sent from one user
+ to another.</p>
+ <p>The first user "sends" the message in the example above by:</p>
+ <code type="none">
+messenger:message(fred, "hello")</code>
+ <p>After testing that the client process exists:</p>
+ <code type="none">
+whereis(mess_client) </code>
+ <p>and a message is sent to <c>mess_client</c>:</p>
+ <code type="none">
+mess_client ! {message_to, fred, "hello"}</code>
+ <p>The client sends the message to the server by:</p>
+ <code type="none">
+{messenger, messenger@super} ! {self(), message_to, fred, "hello"},</code>
+ <p>and waits for a reply from the server.</p>
+ <p>The server receives this message and calls:</p>
+ <code type="none">
+server_transfer(From, fred, "hello", User_List),</code>
+ <p>which checks that the pid <c>From</c> is in the <c>User_List</c>:</p>
+ <code type="none">
+lists:keysearch(From, 1, User_List) </code>
+ <p>If <c>keysearch</c> returns the atom <c>false</c>, some sort of
+ error has occurred and the server sends back the message:</p>
+ <code type="none">
+From ! {messenger, stop, you_are_not_logged_on}</code>
+ <p>which is received by the client which in turn does
+ <c>exit(normal)</c> and terminates. If <c>keysearch</c> returns
+ <c>{value,{From,Name}}</c> we know that the user is logged on and
+ is his name (peter) is in variable <c>Name</c>. We now call:</p>
+ <code type="none">
+server_transfer(From, peter, fred, "hello", User_List)</code>
+ <p>Note that as this is <c>server_transfer/5</c> it is not the same
+ as the previous function <c>server_transfer/4</c>. We do another
+ <c>keysearch</c> on <c>User_List</c> to find the pid of the client
+ corresponding to fred:</p>
+ <code type="none">
+lists:keysearch(fred, 2, User_List)</code>
+ <p>This time we use argument 2 which is the second element in
+ the tuple. If this returns the atom <c>false</c> we know that
+ fred is not logged on and we send the message:</p>
+ <code type="none">
+From ! {messenger, receiver_not_found};</code>
+ <p>which is received by the client, if <c>keysearch</c> returns:</p>
+ <code type="none">
+{value, {ToPid, fred}}</code>
+ <p>we send the message:</p>
+ <code type="none">
+ToPid ! {message_from, peter, "hello"}, </code>
+ <p>to fred's client and the message:</p>
+ <code type="none">
+From ! {messenger, sent} </code>
+ <p>to peter's client.</p>
+ <p>Fred's client receives the message and prints it:</p>
+ <code type="none">
+{message_from, peter, "hello"} ->
+ io:format("Message from ~p: ~p~n", [peter, "hello"])</code>
+ <p>and peter's client receives the message in
+ the <c>await_result</c> function.</p>
+ </section>
+</chapter>
+