[Erlang Systems]

4 Servers

This section describes a simple and powerful way of programming client-server applications. Client-server applications are programmed using the gen_server behaviour.

Refer to the Reference Manual , the module gen_server in stdlib, for full details of the behaviour interface.

4.1 Client-Server Principles

This section describes several solutions to one sample problem in order to illustrate how to write client-server applications.

The sample problem is a very simple server which acts as a Home Location Register (HLR). We will implement a small sub-set of an HLR which we call VSHLR (Very Simple HLR) in a number of different ways. The Erlang modules which implement our VSHLR will always be called something like vshlr_XX. All these modules will export the following functions:

The client-server model can be illustrated in the following figure:

clientserver
The Client-Server Model

The client-server model is characterized by a central server and an arbitrary number of clients. The client-server model is generally used for resource management operations, where several different clients want to share a common resource. The server is responsible for managing this resource.

If we ignore how the server is started and stopped, and ignore all error cases, then it is possible to describe the server by means of a simple function f.

Suppose that the internal state of the server is described by the state variable S and that the server receives a query Q. The server responds by sending a reply R back to the client and changes its internal state to S'. This can be described as follows:

{R, S'} = f(Q, S)

Given a function f, we can write a very simple universal client server as follows:

-module(server).
-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
-vsn('$Revision: /main/release/2 $').

-export([start/3, stop/1, loop/3, call/2]).

start(Name, F, State) ->
    register(Name, spawn(server, loop, [Name, F, State])).

stop(Name) ->
    exit(whereis(Name), kill).

call(Name, Query) ->
    Name ! {self(), Query},
    receive
        {Name, Reply} ->
            Reply
    end.

loop(Name, F, State) ->
    receive
        {Pid, Query} ->
            {Reply, State1} = F(Query, State),
            Pid ! {Name, Reply},
            loop(Name, F, State1)
    end.

vshlr can be written using server:

-module(vshlr_1).
-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
-vsn('$Revision: /main/release/2 $').

-export([start/0, stop/0, i_am_at/2, find/1, handle_event/2]).

start() -> server:start(xx1, 
                        fun(Event, State) -> 
                            handle_event(Event, State) 
                        end,
                        []).

stop() -> server:stop(xx1).

i_am_at(Person, Position) -> server:call(xx1, {i_am_at, Person, Position}).
find(Person)              -> server:call(xx1, {find, Person}).


handle_event({i_am_at, Person, Position}, State) ->
    State1 = update_position(Person, Position, State),
    {ok, State1};
handle_event({find, Person}, State) ->
    Location = lookup(Person, State),
    {Location, State}.

update_position(Key, Value, [{Key, _}|T]) -> [{Key, Value}|T];
update_position(Key, Value, [H|T])        -> [H|update_position(Key, Value, T)];
update_position(Key, Value, [])           -> [{Key,Value}].

lookup(Key, [{Key, Value}|_]) -> {at, Value};
lookup(Key, [_|T])            -> lookup(Key, T);
lookup(Key, [])               -> lost.

We can run this as follows:

1 > vshlr_1:start().
true
2> vshlr_1:i_am_at("joe", "home").
ok
3> vshlr_1:i_am_at("helen", "work").    
ok
4> vshlr_1:find("joe").
{at,"home"}
5> vshlr_1:find("mike").
lost
6> vshlr_1:i_am_at("joe", {building,23}).
ok
7> vshlr_1:find("helen").                
{at,"work"}
8> vshlr_1:find("joe").  
{at,{building,23}}

Even though our VSHLR program is extremely simple, it illustrates and provides simple solutions to a surprisingly large number of design issues.

The reader should note the following:

4.1.1 Extending the Server

Splitting a server into two parts means that we can work on either of the parts without effecting the other. We can illustrate this by extending the server so that it logs the last ten requests and calls the error logger if something goes wrong. This version is called server1 to distinguish it from server.

-module(server1).
-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
-vsn('$Revision: /main/release/2 $').

-export([start/3, stop/1, loop/4, call/2]).

start(Name, F, State) ->
    register(Name, spawn(server1, loop, [Name, F, State, []])).

stop(Name) ->
    exit(whereis(Name), kill).

call(Name, Query) ->
    Name ! {self(), Query},
    receive
      {Name, error} ->
        exit(server_error);
      {Name, {ok, Reply}} ->
        Reply
    end.

loop(Name, F, State, Buff) ->
    receive
      {Pid, Query} ->
        Buff1 = trim([Query|Buff]),
        case catch F(Query, State) of
          {'EXIT', Why} ->
            Pid ! {Name, error},
            error_logger:error_msg({server_error, Name, Buff1});
          {Reply, State1} ->
            Pid ! {Name, {ok, Reply}},
            loop(Name, F, State1, Buff1)
        end
    end.

trim([X1,X2,X3,X4,X5,X6,X7,X8,X9,X10|_]) -> [X1,X2,X3,X4,X5,X6,X7,X8,X9,X10];
trim(X) -> X.


server1 has exactly the same function interface as the previous version of server.

If we use server1 together with vshlr, we get an improved version of vshlr, which has additional error handling facilities.

The improvement to VSHLR was made without any significant change to the code in the module vshlr. This is a consequence of dividing the server into two parts, the generic part which is common to all servers, and the specific part which concerns the VSHLR problem.

4.1.2 The Generic Server

The examples shown in the previous sections make it apparent that the server can be extended in a number of different ways. The module gen_server provides a number of useful extensions to our simple server. In the following example, vshrl is re-implemented using gen_server.

The Reference Manual, stdlib, module gen_server has detailed information about the generic server.

-module(vshlr_2).
-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
-vsn('$Revision: /main/release/2 $').

-export([start/0, start_link/0, stop/0, i_am_at/2, find/1]).

-behaviour(gen_server).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).

%% These are the interface routines 

start() -> gen_server:start({local, xx2}, vshlr_2, [], []).
start_link() -> gen_server:start_link({local, xx2}, vshlr_2, [], []).

stop()  -> gen_server:call(xx2, die, 10000).

i_am_at(Person, Location) -> 
    gen_server:call(xx2, {i_am_at, Person, Location}, 10000).

find(Person)  -> 
    gen_server:call(xx2, {find, Person}, 10000).

%% These Routine MUST be exported since they are called by gen_server

init(_) -> {ok, []}.

handle_call({i_am_at, Person, Location}, _, State) ->
    State1 = update_location(Person, Location, State),
    {reply, ok, State1};
handle_call({find, Person}, _, State) ->
    Location = lookup(Person, State),
    {reply, Location, State};
handle_call(die, _, State) ->
    %% ok goes back to the user and terminate(normal, State)
    %% will be called
    {stop,  normal, ok, State}.

handle_cast(Request, State) -> {noreply, State}.

handle_info(Request, State) -> {noreply, State}.

terminate(Reason, State) -> 
    ok.

%% sub-functions

update_location(Key, Value, [{Key, _}|T]) -> [{Key, Value}|T];
update_location(Key, Value, [H|T])        -> [H|update_location(Key, Value, T)];
update_location(Key, Value, [])           -> [{Key,Value}].

lookup(Key, [{Key, Value}|_]) -> {at, Value};
lookup(Key, [_|T])            -> lookup(Key, T);
lookup(Key, [])               -> lost.

The flow of control in the example shown above is as follows:

4.1.3 Local Client-Server

The example shown in the previous section was a local server. The main points to note were:

4.1.4 Global Client-Server

To make a global server, the following small changes are made to the access routines:

With these changes, the client-server model will work in a network of distributed nodes. All nodes in the system are assumed to evaluate identical copies of the code. The server will be placed on the first node which evaluates gen_server:start. All other nodes will be coupled to this node automatically.

4.1.5 Anonymous Server

The following calls will start an anonymous server:

The user must ensure that the Pid of the server is communicated to all clients which make use of the server.

4.2 Notes


Copyright © 1991-1999 Ericsson Utvecklings AB