This chapter should be read in conjunction with gen_server(3)
, where all interface functions and callback functions are described in detail.
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.
An example of a simple server written in plain Erlang was given in Overview. The server can be re-implemented using gen_server
, resulting in this callback module:
java 代码
- -module(ch3).
- -behaviour(gen_server).
- -export([start_link/0]).
- -export([alloc/0, free/1]).
- -export([init/1, handle_call/3, handle_cast/2]).
- start_link() ->
- gen_server:start_link({local, ch3}, ch3, [], []).
- alloc() ->
- gen_server:call(ch3, alloc).
- free(Ch) ->
- gen_server:cast(ch3, {free, Ch}).
- init(_Args) ->
- {ok, channels()}.
- handle_call(alloc, _From, Chs) ->
- {Ch, Chs2} = alloc(Chs),
- {reply, Ch, Chs2}.
- handle_cast({free, Ch}, Chs) ->
- Chs2 = free(Ch, Chs),
- {noreply, Chs2}.
The code is explained in the next sections.
In the example in the previous section, the gen_server is started by calling ch3:start_link()
:
java 代码
- start_link() ->
- gen_server:start_link({local, ch3}, ch3, [], []) => {ok, Pid}
start_link
calls the function gen_server:start_link/4
. This function spawns and links to a new process, a gen_server.
{local, ch3}
specifies the name. In this case, the gen_server will be locally registered as ch3
.{global, Name}
, in which case the gen_server is registered using global:register_name/2
.ch3
, is the name of the callback module, that is the module where the callback functions are located.start_link
, alloc
and free
) are located in the same module as the callback functions (init
, handle_call
and handle_cast
). This is normally good programming practice, to have the code corresponding to one process contained in one module.init
. Here, init
does not need any indata and ignores the argument.gen_server(3)
for available options.If name registration succeeds, the new gen_server process calls the callback function ch3:init([])
. init
is expected to return {ok, State}
, where State
is the internal state of the gen_server. In this case, the state is the available channels.
java 代码
- init(_Args) ->
- {ok, channels()}.
Note that gen_server:start_link
is synchronous. It does not return until the gen_server has been initialized and is ready to receive requests.
gen_server:start_link
must be used if the gen_server is part of a supervision tree, i.e. is started by a supervisor. There is another function gen_server:start
to start a stand-alone gen_server, i.e. a gen_server which is not part of a supervision tree.
The synchronous request alloc()
is implemented using gen_server:call/2
:
java 代码
- alloc() ->
- gen_server:call(ch3, alloc).
ch3
is the name of the gen_server and must agree with the name used to start it. alloc
is the actual request.
The request is made into a message and sent to the gen_server. When the request is received, the gen_server calls handle_call(Request, From, State)
which is expected to return a tuple {reply, Reply, State1}
. Reply
is the reply which should be sent back to the client, and State1
is a new value for the state of the gen_server.
java 代码
- handle_call(alloc, _From, Chs) ->
- {Ch, Chs2} = alloc(Chs),
- {reply, Ch, Chs2}.
In this case, the reply is the allocated channel Ch
and the new state is the set of remaining available channels Chs2
.
Thus, the call ch3:alloc()
returns the allocated channel Ch
and the gen_server then waits for new requests, now with an updated list of available channels.
The asynchronous request free(Ch)
is implemented using gen_server:cast/2
:
java 代码
- free(Ch) ->
- gen_server:cast(ch3, {free, Ch}).
ch3
is the name of the gen_server. {free, Ch}
is the actual request.
The request is made into a message and sent to the gen_server. cast
, and thus free
, then returns ok
.
When the request is received, the gen_server calls handle_cast(Request, State)
which is expected to return a tuple {noreply, State1}
. State1
is a new value for the state of the gen_server.
java 代码
- handle_cast({free, Ch}, Chs) ->
- Chs2 = free(Ch, Chs),
- {noreply, Chs2}.
In this case, the new state is the updated list of available channels Chs2
. The gen_server is now ready for new requests.
If the gen_server is part of a supervision tree, no stop function is needed. The gen_server will automatically be terminated by its supervisor. Exactly how this is done is defined by a shutdown strategy set in the supervisor.
If it is necessary to clean up before termination, the shutdown strategy must be a timeout value and the gen_server must be set to trap exit signals in the init
function. When ordered to shutdown, the gen_server will then call the callback function terminate(shutdown, State)
:
java 代码
- init(Args) ->
- ...,
- process_flag(trap_exit, true),
- ...,
- {ok, State}.
- ...
- terminate(shutdown, State) ->
- ..code for cleaning up here..
- ok.
If the gen_server is not part of a supervision tree, a stop function may be useful, for example:
java 代码
- ...
- export([stop/0]).
- ...
- stop() ->
- gen_server:cast(ch3, stop).
- ...
- handle_cast(stop, State) ->
- {stop, normal, State};
- handle_cast({free, Ch}, State) ->
- ....
- ...
- terminate(normal, State) ->
- ok.
The callback function handling the stop
request returns a tuple {stop, normal, State1}
, where normal
specifies that it is a normal termination and State1
is a new value for the state of the gen_server. This will cause the gen_server to call terminate(normal,State1)
and then terminate gracefully.
If the gen_server should be able to receive other messages than requests, the callback function handle_info(Info, State)
must be implemented to handle them. Examples of other messages are exit messages, if the gen_server is linked to other processes (than the supervisor) and trapping exit signals.
java 代码
- handle_info({'EXIT', Pid, Reason}, State) ->
- ..code to handle exits here..
- {noreply, State1}.