使用 gen 服务器行为

gen_server 是一个像服务器一样工作的特定有限状态机。gen_server 可以处理不同类型的事件:

  • handle_call 的同步请求
  • handle_cast 的异步请求
  • handle_info 的其他消息(未在 OTP 规范中定义)

同步和异步消息在 OTP 中指定,并且是带有任何类型数据的简单标记元组。

一个简单的 gen_server 定义如下:

-module(simple_gen_server).
-behaviour(gen_server).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

start_link() ->
    Return = gen_server:start_link({local, ?MODULE}, ?MODULE, [], []),
    io:format("start_link: ~p~n", [Return]),
    Return.

init([]) ->
    State = [],
    Return = {ok, State},
    io:format("init: ~p~n", [State]),
    Return.

handle_call(_Request, _From, State) ->
    Reply = ok,
    Return = {reply, Reply, State},
    io:format("handle_call: ~p~n", [Return]),
    Return.

handle_cast(_Msg, State) ->
    Return = {noreply, State},
    io:format("handle_cast: ~p~n", [Return]),
    Return.

handle_info(_Info, State) ->
    Return = {noreply, State},
    io:format("handle_info: ~p~n", [Return]),
    Return.

terminate(_Reason, _State) ->
    Return = ok,
    io:format("terminate: ~p~n", [Return]),
    ok.

code_change(_OldVsn, State, _Extra) ->
    Return = {ok, State},
    io:format("code_change: ~p~n", [Return]),
    Return.

此代码很简单:收到的每条消息都打印到标准输出。

gen_server 行为

要定义 gen_server,你需要使用 -behaviour(gen_server) 在源代码中明确声明它。注意,behaviour 可以用美国(行为)或英国(行为)来写。

此函数是调用另一个函数的简单快捷方式:gen_server:start_link/3,4

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

当你想要启动链接到 supervisor 或其他进程的服务器时,将调用此函数。start_link/3,4 可以自动注册你的过程(如果你认为你的过程需要是唯一的),或者可以像简单的过程那样简单地生成它。调用时,此函数执行 init/1

此函数可以返回以下定义值:

  • {ok,Pid}
  • ignore
  • {error,Error}

INIT / 1

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

init/1 是你的服务器启动时第一个执行的功能。这个初始化应用程序的所有先决条件并将状态返回到新创建的进程。

此函数只能返回这些定义的值:

  • {ok,State}
  • {ok,State,Timeout}
  • {ok,State,hibernate}
  • {stop,Reason}
  • ignore

State 变量可以是所有东西,(例如,列表,元组,支柱,地图,记录),并且对于生成的进程内的所有函数都可以访问。

handle_call / 3

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

gen_server:call/2 执行此回调。第一个参数是你的消息(_Request),第二个参数是请求的起源(_From),最后一个参数是你运行的 gen_server 行为的当前状态(State)。

如果你想回复调用者,handle_call/3 需要返回以下数据结构之一:

  • {reply,Reply,NewState}
  • {reply,Reply,NewState,Timeout}
  • {reply,Reply,NewState,hibernate}

如果你不想回复调用者,handle_call/3 需要返回以下数据结构之一:

  • {noreply,NewState}
  • {noreply,NewState,Timeout}
  • {noreply,NewState,hibernate}

如果要停止当前 gen_server 的当前执行,handle_call/3 需要返回以下数据结构之一:

  • {stop,Reason,Reply,NewState}
  • {stop,Reason,NewState}

handle_cast / 2

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

gen_server:cast/2 执行此回调。第一个参数是你的消息(_Msg),第二个参数是你正在运行的 gen_server 行为的当前状态。

默认情况下,此函数无法向调用者提供数据,因此,你只有两个选择,继续当前执行:

  • {noreply,NewState}
  • {noreply,NewState,Timeout}
  • {noreply,NewState,hibernate}

或者停止当前的 gen_server 流程:

  • {stop,Reason,NewState}

handle_info / 2

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

当非标准 OTP 消息来自外部世界时,执行 handle_info/2。这个不能回复,像 handle_cast/2 只能做 2 个动作,继续当前执行:

  • {noreply,NewState}
  • {noreply,NewState,Timeout}
  • {noreply,NewState,hibernate}

或者停止当前运行的 gen_server 进程:

  • {stop,Reason,NewState}

终止/ 2

terminate(_Reason, _State) ->
    ok.

发生错误或想要关闭 gen_server 进程时调用 terminate/2

code_change / 3

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

当你想要升级正在运行的代码时,会调用 code_change/3 函数。

此函数只能返回这些定义的值:

  • {ok, NewState}
  • {error, Reason}

开始这个过程

你可以编译代码并启动 simple_gen_server

simple_gen_server:start_link().

如果要将消息发送到服务器,可以使用以下功能:

% will use handle_call as callback and print:
%   handle_call: mymessage
gen_server:call(simple_gen_server, mymessage).

% will use handle_cast as callback and print:
%   handle_cast: mymessage
gen_server:cast(simple_gen_server, mymessage).

% will use handle_info as callback and print:
%   handle_info: mymessage
erlang:send(whereis(simple_gen_server), mymessage).