Http://blog.sina.com.cn/s/blog_3fe961ae0101k4p6.html
Behavior patterns are very similar to interfaces in object-oriented languages, at least as I understand them. The OTP behavior pattern divides some recurring patterns into two parts, the generic part and the implementation-specific part of the application, which is similar to the process of abstracting out an interface in object-oriented programming. This article gives an example of the most common behavior pattern in OTP: General server, or Gen_server.
Writing the Gen_server callback module roughly includes a 3-phase step:
(1) Determine the name of the callback module;
(2) Write the interface function (called by the client);
(3) Implement the 6 callback functions of Gen_server (called by the Gen_server container) in the callback module.
Here is an example code in Erlang OTP concurrency programming, which implements a simple RPC service by implementing the Gen_server interface, which allows the client to invoke any function exported from any module on the server side. A get_count query interface is provided to query the number of requests that have been processed by the current server. In addition, Start_link () and stop () are used to stop the server process.
%%%-------------------------------------------------------------------
%%% @author Martin & Eric
%%% [http://www.erlware.org]
%%% @copyright 2008-2010 Erlware
%%% @doc RPC over TCP server. This module defines a server process
%%% listens for incoming TCP connections and allows the user to
%%% execute RPC commands via that TCP stream.
%%% @end
%%%-------------------------------------------------------------------
-module (Tr_server).
-behaviour (Gen_server).
Percent percent API
-export ([
START_LINK/1,
start_link/0,
get_count/0,
stop/0
]).
Gen_server Callbacks
-export ([Init/1, HANDLE_CALL/3, HANDLE_CAST/2, HANDLE_INFO/2,
TERMINATE/2, CODE_CHANGE/3]).
-define (SERVER,? MODULE).
-define (Default_port, 1055).
-record (state, {port, lsock, Request_count = 0}).
%%%===================================================================
%%% API
%%%===================================================================
%%--------------------------------------------------------------------
Percent @doc starts the server.
%%
Percent @spec Start_link (Port::integer ()) {OK, Pid}
Percent of where
Percent PID = pid ()
Percent @end
%%--------------------------------------------------------------------
Start_link (Port),
Gen_server:start_link ({local,? SERVER},? MODULE, [Port], []).
Percent @spec Start_link () {OK, Pid}
Percent @doc Calls ' Start_link (port) ' using the default port.
Start_link ()
Start_link (? Default_port).
%%--------------------------------------------------------------------
Percent @doc fetches the number of requests made to this server.
Percent @spec get_count () {OK, count}
Percent of where
Percent Count = integer ()
Percent @end
%%--------------------------------------------------------------------
Get_count ()
Gen_server:call (? SERVER, get_count).
%%--------------------------------------------------------------------
Percent @doc Stops the server.
Percent @spec Stop (), OK
Percent @end
%%--------------------------------------------------------------------
Stop ()
Gen_server:cast (? SERVER, stop).
%%%===================================================================
%%% Gen_server Callbacks
%%%===================================================================
Init ([Port])
{OK, lsock} = Gen_tcp:listen (Port, [{active, true}]),
{OK, #state {port = port, Lsock = Lsock}, 0}.
Handle_call (get_count, _from, State)
{reply, {OK, state#state.request_count}, state}.
Handle_cast (stop, state),
{Stop, Normal, state}.
Handle_info ({TCP, Socket, rawdata}, state),
Do_rpc (Socket, RawData),
RequestCount = State#state.request_count,
{noreply, State#state{request_count = RequestCount 1}};
Handle_info (Timeout, #state {lsock = lsock} = state)
{OK, _sock} = gen_tcp:accept (Lsock),
{noreply, State}.
Terminate (_reason, _state)
Ok.
Code_change (_OLDVSN, State, _extra)
{OK, state}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
Do_rpc (Socket, RawData)
Try
{M, F, A} = SPLIT_OUT_MFA (RawData),
Result = Apply (M, F, A),
Gen_tcp:send (Socket, Io_lib:fwrite ("~p~n", [Result]))
Catch
_class:err
Gen_tcp:send (Socket, Io_lib:fwrite ("~p~n", [ERR]))
End.
SPLIT_OUT_MFA (RawData)
MFA = Re:replace (RawData, "\r\n$", "" ", [{return, List}]),
{match, [M, F, A]} =
Re:run (MFA,
"(. *):(. *) \s*\\ ((. *) \s*\\) \s*.\s*$",
[{capture, [+], list}, Ungreedy]),
{List_to_atom (M), List_to_atom (F), Args_to_terms (A)}.
Args_to_terms (Rawargs)
{OK, toks, _line} = erl_scan:string ("[" Rawargs "].", 1),
{OK, Args} = Erl_parse:parse_term (Toks),
Args.
The author runs this program in a Linux environment:
1> C (tr_server).
{Ok,tr_server}
2> Tr_server:start_link ().
{Ok,<0.39.0>}
3>
Here you need to start a shell and enter:
[Email protected]:~# telnet 127.0.0.1 1055
Trying 127.0.0.1 ...
Connected to 127.0.0.1.
Escape character is ' ^] '.
Then go back to the Erlang console and enter:
3> Tr_server:get_count ().
{ok,0}
4> tr_server:stop ().
Ok
Why first connect to port 1055 with Telnet? Analyze the behavior of the init ([Port]) function. The init ([Port]) function first establishes a TCP listener socket on the specified port with the GEN_TCP module in the standard library:
{OK, lsock} = Gen_tcp:listen (Port, [{active, true}]),
The init ([Port]) function then returns a ternary group containing the atomic OK, the initial process state, and the number 0:
{OK, #state {port = port, Lsock = Lsock}, 0}.
This 0 indicates a timeout value. Setting the timeout to zero causes the Gen_server container to trigger a timeout immediately after the end of INIT/1, forcing the process to process the timeout message (completed by HANDLE_INFO/2) the first time after the initialization is complete. The purpose of using 0 here is to wake the server and perform some of the specified actions: Wait for the connection on the listener socket to be created. Gen_server will always block here when no connection is received, so if you send a tr_server:get_count () request at this point, you will get a timeout feedback:
* * Exception exit: {Timeout,{gen_server,call,[tr_server,get_count]}}
The RPC service implemented by Tr_server can theoretically call any function exported from any module on the server side. For example, you can enter in Telnet:
Init:stop ().
Return:
Ok
Connection closed by foreign host.
This is because Init:stop () shuts down the entire Erlang node running the RPC server.
Finally, let's look at some of the commonly used callback functions in Gen_server. Open Gen_server Source (on the author's Windows system, this file is located in C:\Program files (x86) \erl5.8.5\lib\stdlib-1.17.5\src), in the header notes of the file, The format of the parameters to be returned by each interface and the execution process of gen_server are described in detail.
%%% ---------------------------------------------------
%%%
%%% The idea behind the user module
%%% provides (different) functions to handle different
%%% kind of inputs.
%%% If The Parent process terminates the MODULE:TERMINATE/2
%%% function is called.
%%%
%%% the user module should export:
%%%
%%% Init (Args)
%%% ==> {OK, state}
%%% {OK, state, Timeout}
%%% Ignore
%%% {stop, Reason}
%%%
%%% Handle_call (MSG, {from, tags}, state)
%%%
%%% ==> {reply, reply, state}
%%% {reply, reply, state, Timeout}
%%% {noreply, State}
%%% {noreply, state, Timeout}
%%% {stop, Reason, Reply, state}
%%% Reason = Normal | Shutdown | term terminate (state) is called
%%%
%%% Handle_cast (MSG, State)
%%%
%%% ==> {noreply, State}
%%% {noreply, state, Timeout}
%%% {Stop, Reason, state}
%%% Reason = Normal | Shutdown | term terminate (state) is called
%%%
%%% Handle_info (info, state) info is e.g. {' EXIT ', P, R}, {Nodedown, N}, ...
%%%
%%% ==> {noreply, State}
%%% {noreply, state, Timeout}
%%% {Stop, Reason, state}
%%% Reason = Normal | Shutdown | term, terminate (state) is called
%%%
%%% Terminate (Reason, state) Let the user module clean up
%%% always called when server terminates
%%%
%%% ==> OK
%%%
%%%
%%% the work flow (of the server) can described as follows:
%%%
%%% User Module Generic
%%% ----------- -------
%%% Start-----> Start
%%% Init <-----.
%%%
%%% Loop
%%% Handle_call <-----.
%%%-----> Reply
%%%
%%% Handle_cast <-----.
%%%
%%% Handle_info <-----.
%%%
%%% Terminate <-----.
%%%
%%%-----> Reply
%%%
%%%
%%% ---------------------------------------------------
Reference: Erlang OTP Concurrent Programming combat
Erlang OTP Programming first experience--gen_server and behavioral patterns