Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

The full example of Erlang


May 13, 2021 Erlang


Table of contents


A complete example of Erlang

The next example is a simple messager example. M essager is an application that allows you to log on to different nodes and send messages to each other.

Before you begin, note the following:

  • This example only demonstrates the logic of messaging--- does not provide a user-friendly interface (although this can be done at Erlang).
  • This type of problem can be easily implemented using OTP tools, as well as providing methods for online updates, etc. (Refer to OTP design principles)
  • This sample program is incomplete and does not take into account the departure of nodes, etc. T his issue will be fixed in a later release.

Messager allows "clients" to connect to and identify themselves to a centralized server. T hat is, the user does not need to know the name of the Erlang node where the other user is located to send the message.

The messager.erl file reads as follows:

%%% 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 that 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.

Before you can use this sample program, you need to:

  • Configure server_node() function.
  • Copy the compiled code (messager.beam) to every computer on which you started Erlang.

In this next example, we started the Erlang node on four different calculations. I f your network doesn't have that many computers, you can also start multiple nodes on the same computer.

The four nodes that start are: messager@super, c1@bilo, c2@kosken, c3@gollum.

Start by starting meesager@super program on the server:

(messenger@super)1> messenger:start_server().
true

The next step is to sign in c1@bibo in :

(c1@bilbo)1> messenger:logon(peter).
true
logged_on

James then logs in c2@kosken on the website:

(c2@kosken)1> messenger:logon(james).
true
logged_on

Finally, sign in to c3@gollum with Fred:

(c3@gollum)1> messenger:logon(fred).
true
logged_on

Now Peter can send a message to Fred:

(c1@bilbo)2> messenger:message(fred, "hello").
ok
sent

When Fred receives the message, reply to a message to Peter and log out:

Message from peter: "hello"
(c3@gollum)2> messenger:message(peter, "go away, I'm busy").
ok
sent
(c3@gollum)3> messenger:logoff().
logoff

Then, when James sends a message to Fred, the following occurs:

(c2@kosken)2> messenger:message(fred, "peter doesn't like you").
ok
receiver_not_found

Sending a message failed because Fred had already left.

Let's start by looking at some of the new concepts introduced in this example.

There are two versions of server_transfer function: one with four arguments (server_transfer/4) and the other with five parameters (server_transfer/5). E rlang sees them as two completely different functions.

Notice here how to server_transfer the function itself through the server User_List, and here's a loop. T he Erlang compiler is very clever in optimizing the above code into a loop rather than an illegal rule-of-procedure function call. H owever, it can only work if there is no other code after the function call (Note: tail-pass regulation).

The functions in the lists module are used in the example. T he lists module is a very useful module, and it is recommended that you take a closer look at it through the user manual (erl-man lists).

Lists: Keymeber (Key, Position, Lists) functions traverse the metagroups in the list, view the data at the specified location (Position) of each dollar group, and determine whether the metagroup is equal to Key. T he position of the first element in the metagroup is 1, and so on. I f you find that the element at the Position location of a meta-group is the same as Key, true is returned, otherwise false is returned.

3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
true
4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
false

Lists:keydelete is very similar to lists:keymember, except that it removes the first mets found in the list, if one exists, and returns the remaining list:

5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
[{x,y,z},{b,b,b},{q,r,s}]

Lists:keysearch is similar to lists:keymember, but it will return either the value, Tuple_Found, or the atomic value false.

There are many useful functions in the lists module.

The Erlang process (conceptually) runs until it executes the receive command, when there are no messages in the message queue that it wants to receive.
Here, "conceptually" because the processes that are active in the Erlang system actually share CPU processing time.

When a process has nothing to do, that is, when one function calls return to return and no other function is called, the process ends. A nother way to terminate a process is to call the exit/1 function. T he parameters of the exit/1 function have special meanings, which we'll discuss later. I n this example, the exit (normal) is used to end the process, which has the same effect as the program terminates because it does not call the function again.

The built-in function whereis (RegisteredName) checks to see if a process has registered the process name RegisteredName. I f it already exists, the process identifier of the return process is returned. I f it does not exist, the atomic value undefined is returned.

By this point, you should already be able to read most of the code for the messager module. L et's dive into the detailed process of sending a message from one user to another.

When the first user calls "sends" to send a message:

messenger:message(fred, "hello")

First check if the user itself is running in the system (whether the mess_client process can be found):

whereis(mess_client) 

If the user exists, send the message to mess_client:

mess_client ! {message_to, fred, "hello"}

The client sends the message to the server using the following code:

{messenger, messenger@super} ! {self(), message_to, fred, "hello"},

Then wait for a reply from the server. W hen the server receives the message, it calls:

{messenger, messenger@super} ! {self(), message_to, fred, "hello"},

Next, check whether the process identifier From is in the list of User_Lists code:

lists:keysearch(From, 1, User_List)

If keysearch returns the atomic value false, some error occurs and the service returns the following message:

From ! {messenger, stop, you_are_not_logged_on}

When client receives this message, the exit is executed and the program is terminated. I f keysearch returns a value, from, Nmae, you can be sure that the user is logged in and that his or her name (peter) is stored in the variable Name.

Next call:

server_transfer(From, peter, fred, "hello", User_List)

Note that here is server_transfer/5 of the function, server_transfer/4 is not the same function. T he keysearch function is also called again to User_List process identifier corresponding to fred in the User_List:

lists:keysearch(fred, 2, User_List)

This time parameter 2 is used, which means that it is the second element in the metagroup. I f the atomic value false is returned, fred is dened and the server sends the following message to the process that sent the message:

From ! {messenger, receiver_not_found};

Client will receive the message.

If keysearch returns a value of:

{value, {ToPid, fred}}

The following message is sent to the fred client:

ToPid ! {message_from, peter, "hello"}, 

The following message is sent to peter's client:

From ! {messenger, sent} 

Fred's client receives the message and outputs it:

{message_from, peter, "hello"} ->
    io:format("Message from ~p: ~p~n", [peter, "hello"])

The peter client await_result reply in the function.