erlang四大行为模式
文章有转载复制其他博客内容,如有侵权,请联系删除
首先来简单说下 行为模式, **行为模式(behaviour)
**是面向进程编程中各种常见模式的一种形似化表述.
OTP行为模式 将 一些反复出现的模式分成了两部分: 通用部分 和 具体应用相关的实现部分, 二者通过一套简单明确的接口进行通信.
- 行为模式的组成部分:
- 行为模式接口: 是一组特定的函数和相关调用规范. gen_server行为模式接口:
init/
,handle_call/3
,handle_cast/2
,handle_info/2
,terminate/2
,code_change/3
- 行为模式实现: 是程序员提供的具体应用相关的代码, 导出了接口所需的全部函数的回调函数.
- 行为模式容器: 就是一个进程, 会调用与行为模式实现相对应的回调模块处理应用相关的逻辑.
- 行为模式实例化: 启动一个新容器进程.
- OTP行为模式的好处:
- 可以大大缩减代码量, 开发者可以用更少的代码完成更多的事情
- 代码稳定可靠, 坚如磐石, 核心库代码经过了严酷的测试
- 代码可以被嵌入更发, 提供更强劲功能的OTP框架, 如监督树
- 在了解某种模式后, 相同行为模式的实现代码更容易理解
erlang四大行为模式:
- gen_server: 通用服务器
- gen_event: 通用事件处理
- gen_fsm: 有限状态机/ gen_statem: 泛型状态机
- supervisor: 通用监督者
gen_server
通用服务器
gen_server 实现C/S模型, 把behaviour 和 回调模块需要完成的职责分离开, 用于多个客户共用一个资源的情况.
1. gen_event提供的主要的库函数 及 回调函数
gen_server module Callback module Desc
----------------- --------------- ----------------
gen_server:start
gen_server:start_link -----> Module:init/1 启动gen_server
gen_server:stop -----> Module:terminate/2 终止gen_server
gen_server:call
gen_server:multi_call -----> Module:handle_call/3 gen_server同步调用,
gen_server:cast
gen_server:abcast -----> Module:handle_cast/2 gen_server异步调用
- -----> Module:handle_info/2 gen_server进程默认方式接收到消息的处理
- -----> Module:terminate/2
- -----> Module:code_change/3
- 启动服务器
%% 启动函数 gen_server:start/3, gen_server:start/4, gen_server:start_link/3, gen_server:start_link/4, gen_server:start_monitor/3, gen_server:start_monitor/4
%% gen_server:start_link/3, gen_server:start_link/4启动函数创建的gen_server作为监督树的一部分, 链接到监督者
gen_server:start(Module :: module(), Args :: term(), Options :: [start_opt()] ) -> start_ret().
gen_server:start(ServerName :: server_name(), Module :: module(), Args :: term(), Options :: [start_opt()] ) -> start_ret().
gen_server:start_link(Module :: module(), Args :: term(), Options :: [start_opt()] ) -> start_ret().
gen_server:start_link(ServerName :: server_name(), Module :: module(), Args :: term(), Options :: [start_opt()] ) -> start_ret().
%% 回调函数
Module:init(Args :: term()) -> %% 初始化进程状态
{ok, State} | %% State 为初始化进程状态, 一般为记录格式, 需要在回调模块头部声明
{ok,State,Timeout} | %% 增加一个超时时间(ms), 在超时后调用handle_info(timeout, State)
{ok,State,hibernate} | %% 服务器初始化完成后就进入休眠
{ok,State,{continue,Continue}} | %% 立即调用handle_continue(Continue, State),
{stop,Reason}.
gen_server:start/4
, gen_server:start_link/4
使用参数ServerName
创建具有注册名的gen_server.
ServerName
指定了进程的名字,格式可以是{local, Name},{global, Name}
,可不指定;
Module
指定了gen_sever
进程的回调模块;
如果ServerName
为{local, Name}
,会通过register/2
将进程注册为Name
。为{global, Name}
则通过global:register_name/2
将进程注册为Name
。
Args
会作为参数传递给Module:init(Args)
;
启动服务器的回调函数是: Module:init/1
, 参数为启动函数的传入的 Args:: term()
.
gen_server的 启动过程是同步的, 启动函数在 Module:init/1
返回之前不会返回.
- 向服务器进程发消息(同步发消息需要获取应答)
%% 同步发送消息
gen_server:call(ServerRef :: server_ref(), Request :: term()) -> Reply :: term().
gen_server:call(ServerRef :: server_ref(), Request :: term(), TimeOut :: timeout()) -> Reply :: term().
%% 回调函数
Module:handle_call(Request :: term(), From :: from(), State) ->
{reply,Reply,NewState} % Reply被发送回客户端请求, 作为返回值返回; NewState 为gen_server新的内部状态
| {reply,Reply,NewState,Timeout} % 超时后会有Module:handle_info/2来处理, 一般gen_server进入休眠
| {reply,Reply,NewState,hibernate} % 进入休眠状态, 等待下一条消息
| {reply,Reply,NewState,{continue,Continue}} %执行Module:handle_continue/2回调函数, Continue为第一个参数
| {noreply,NewState} % 不回复
| {noreply,NewState,Timeout}
| {noreply,NewState,hibernate}
| {noreply,NewState,{continue,Continue}}
| {stop,Reason,Reply,NewState}
| {stop,Reason,NewState}.
%% 异步发送消息
gen_server:cast(ServerRef :: server_ref(), Request :: term()) -> Reply :: term().
gen_server:cast(ServerRef :: server_ref(), Request :: term(), TimeOut :: timeout()) -> Reply :: term().
%% 回调函数
Module:handle_cast(Request :: term(), State)
{noreply,NewState} % 无返回; NewState 为gen_server新的内部状态
| {noreply,NewState,Timeout} % 超时后会有Module:handle_info/2来处理, 一般gen_server进入休眠
| {noreply,NewState,hibernate} % 进入休眠状态, 等待下一条消息
| {noreply,NewState,{continue,Continue}}
| {stop,Reason,NewState}
-
Module:handle_call/3
: 同步请求回调. 每次收到由gen_server:call/2
发送的消息时这个函数就会被调用. 接收3个参数: 消息, From(发消息的进程), 服务器状态State. -
Module:handle_cast/2
: 异步请求回调. 每次收到由gen_server:cast/2
发送异步消息时这个函数就会被调用. 发送完毕后无需等待任何应答. -
Module:handle_info/2
: 未经gen_server:call/2
或gen_server:cast/2
的 带外消息 处理. 依赖于直接消息通信(如套接字 或 端口驱动) 时,handle_info
会被调用处理. 在回调函数返回带有Timeout
超时时间时,handle_info
会处理超时信息 ; 搭配服务器进程中调用erlang:start_timer(Timeout, self(), Msg)
,handle_info
接收超时信息并处理可实现服务器进程的定时器;
...
erlang:start_timer(Timeout, self(), Msg),
...
handle_info({timeout, _Ref, Msg}, State) ->
... %% 超时处理
{noreply, NState};
-
Module:handle_continue/2
: 仅当另一个回调返回包含{continue, Continue}
,handle_continue/2
会被立即调用. 用于将某个功能分布执行. 返回结果与handle_cast/2
类似 -
Module:code_change/3
: 用于在版本升级/降级时更新服务器内部状态. -
Module:terminate/2
: 服务器执行gen_server:stop/2, gen_server:stop/3
时会被调用, 服务器进程正常结束, 返回前可以做一些必要的清理, 会返回Reason终结服务器进程.
2. 使用
-module(tcp_server).
%% API
-export([
start_link/0
]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-include("common.hrl").
-record(state, {num = 0, socket}).
-define(port, 5677).
-define(host, {192,168,0,0}). %% IP
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%% ---------------------------------------------------
%% 回调函数
%% ---------------------------------------------------
init([]) ->
{ok, Listen} = gen_tcp:listen(?port, [binary, {packet, 0}, {reuseaddr, true}, {active, true}]),
accept(Listen),
%% {ok, _Socket} = gen_tcp:accept(Listen),
%% gen_tcp:close(Listen),
State = #state{socket = Listen},
{ok, State}.
handle_call(_Request, _From, State) ->
{noreply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({'EXIT', _, normal}, State) ->
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
accept(Listen) ->
case gen_tcp:accept(Listen) of
{ok, Socket} ->
case tcp_role:start_link(Socket) of
{ok, Pid} ->
?INFO("============== ~w", [Pid]),
case gen_tcp:controlling_process(Socket, Pid) of
ok -> ok;
_ -> ok
end;
_ ->
?INFO("转换失败"),
ok
end;
_ ->
?INFO("监听失败 ===================="),
ok
end,
accept(Listen).
gen_event
通用事件管理器
按照书上定义在OTP中,它由通用事件管理器进程组成,该进程 可 动态添加和删除的任意数量的事件处理程序。事件可以是例如错误,警报或要记录的某些信息
简单来说,就是gen_event行为运行一个了一个事件管理进程,该进程接受消息(事件),并根据消息(事件)做对应的事件处理,而提供的对应事件处理其实就是添加的“回调函数”(事件处理器). 与gen_server不一样的是,gen_event把回调的函数分类成多个module并称为事件处理器.
一个事件管理器可以安装0,1,N个事件处理器,当一个事件管理器接受到一个事件的通知时,这个事件将会被所有的已安装的事件处理器处理(如图)。
1. gen_event提供的主要的库函数 及 回调函数
gen_event函数库 回调函数 描述
---------------- --------------- -----------------
gen_event:start
gen_event:start_monitor
gen_event:start_link -----> - 启动gen_event事件管理器
gen_event:add_handler 增加gen_event事件处理器
gen_event:add_sup_handler -----> Module:init/1 增加gen_event事件处理器, 通过链接来监督连接和调用过程
gen_event:notify 异步触发事件, 会到所有事件处理器中匹配
gen_event:sync_notify -----> Module:handle_event/2 同步触发事件
gen_event:send_request
gen_event:call -----> Module:handle_call/2
- -----> Module:handle_info/2
gen_event:delete_handler -----> Module:terminate/2 删除事件处理器
gen_event:swap_handler 替换事件处理器
gen_event:swap_sup_handler -----> Module1:terminate/2 替换事件处理器, 通过链接来监督连接和调用过程
Module2:init/1
gen_event:which_handlers -----> - 返回事件管理器中所有事件处理器的列表
gen_event:stop -----> Module:terminate/2
- -----> Module:code_change/3
2. 如何使用
- 创建事件管理器
- 向管理器增加事件处理器
- 编写事件处理器程序
- 通知事件管理器
以《erlang趣学指南》的冰球比赛为案例
-module(curling).
-export([start_link/2, set_teams/3, add_points/3, next_round/1]).
-export([join_feed/2, leave_feed/2]).
-export([game_info/1]).
start_link(TeamA, TeamB) ->
%%创建事件管理进程
{ok, Pid} = gen_event:start_link(),
%% 添加事件处理器 gen_event:add_handler(事件管理进程pid, 回调模块, 参数),curling_scoreboard为计分板
gen_event:add_handler(Pid, curling_scoreboard, []),
%% accumulator为累加器
gen_event:add_handler(Pid, curling_accumulator, []),
set_teams(Pid, TeamA, TeamB),
{ok, Pid}.
%%设置队伍
set_teams(Pid, TeamA, TeamB) ->
%%事件通知
gen_event:notify(Pid, {set_teams, TeamA, TeamB}).
%%添加分数
add_points(Pid, Team, N) ->
gen_event:notify(Pid, {add_points, Team, N}).
%%下一回合
next_round(Pid) ->
gen_event:notify(Pid, next_round).
%% Subscribes the pid ToPid to the event feed.
%% The specific event handler for the newsfeed is
%% returned in case someone wants to leave
%%join_feed(Pid, ToPid) ->
%% HandlerId = {curling_feed, make_ref()},
%% gen_event:add_sup_handler(Pid, HandlerId, [ToPid]),
%% HandlerId.
%%
%%leave_feed(Pid, HandlerId) ->
%% gen_event:delete_handler(Pid, HandlerId, leave_feed).
%% Returns the current game state.
game_info(Pid) ->
gen_event:call(Pid, curling_accumulator, game_data).
事件处理器
-module(curling_scoreboard).
-behaviour(gen_event).
-export([init/1, handle_event/2, handle_call/2, handle_info/2, code_change/3,
terminate/2]).
%%计分板
init([]) ->
io:format("curling_scoreboard init ~n"),
{ok, []}.
handle_event({set_teams, TeamA, TeamB}, State) ->
io:format("curling_scoreboard set_teams ~n"),
io:format("Scoreboard: Team ~s vs. Team ~s~n", [TeamA, TeamB]),
%% curling_scoreboard_hw:set_teams(TeamA, TeamB),
{ok, State};
handle_event({add_points, Team, N}, State) ->
io:format("curling_scoreboard add_points ~n"),
[io:format("Scoreboard: increased score of team ~s by 1~n", [Team]) || _ <- lists:seq(1,N)],
%% [curling_scoreboard_hw:add_point(Team) || _ <- lists:seq(1,N)],
{ok, State};
handle_event(next_round, State) ->
io:format("curling_scoreboard next_round ~n"),
io:format("Scoreboard: round over~n"),
%% curling_scoreboard_hw:next_round(),
{ok, State};
handle_event(_, State) ->
{ok, State}.
handle_call(_, State) ->
{ok, ok, State}.
handle_info(_, State) ->
{ok, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(_Reason, _State) ->
ok.
-module(curling_accumulator).
-behaviour(gen_event).
-export([init/1, handle_event/2, handle_call/2, handle_info/2, code_change/3,
terminate/2]).
-record(state, {teams=orddict:new(), round=0}).
%% 累加器
init([]) ->
io:format("accumulator init ~n"),
{ok, #state{}}.
handle_event({set_teams, TeamA, TeamB}, S=#state{teams=T}) ->
io:format("accumulator set_teams ~n"),
Teams = orddict:store(TeamA, 0, orddict:store(TeamB, 0, T)),
{ok, S#state{teams=Teams}};
handle_event({add_points, Team, N}, S=#state{teams=T}) ->
io:format("accumulator add_points ~n"),
Teams = orddict:update_counter(Team, N, T),
{ok, S#state{teams=Teams}};
handle_event(next_round, S=#state{}) ->
io:format("accumulator next_round ~n"),
{ok, S#state{round = S#state.round+1}};
handle_event(_Event, Pid) ->
{ok, Pid}.
handle_call(game_data, S=#state{teams=T, round=R}) ->
{ok, {orddict:to_list(T), {round, R}}, S};
handle_call(_, State) ->
{ok, ok, State}.
handle_info(_, State) ->
{ok, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(_Reason, _State) ->
ok.
supervisor
监督者
supervisor
模块提供了一个监控其他进程的进程。通过supervisor
能够构建监控树,用于构造容错应用程序。监控者能够启动、关闭及重启它的子进程。
监督者的启动
supervisor
进程通过supervisor:start_link(Module, Args)
或supervisor:start_link(SupName, Module, Args)
启动,成功的话返回值为{ok, Pid}
。通过调用回调函数Module:init(Args)
来初始化监supervisor
进程,如果成功,init
将返回{ok, {RestartStrategy, [ChildSpec]}}
RestartStrategy
定义了supervisor
进程的重启策略, 一般是元组{Strategy, Intensity, Period}
Strategy
表示子进程的重启策略,主要有以下几种:
one_for_one
:一个子进程挂掉后,只有该子进程会被重启。默认选项;
one_for_all
:一个子进程挂掉后,其他子进程也会被终止,然后重启所有子进程;
rest_for_one
:一个子进程挂掉后,在该子进程后启动的子进程会被终止,然后重启这些子进程;注意:所有子进程是按照列表顺序去启动的,终止时,按照列表顺序的逆序去终止。
simple_one_for_one
:简单版的one_for_one
,在这种策略下,所有子进程都是动态添加的。此时返回的ChildSpec
只有一个元组.
Intensity
和Period
共同指定supervisor
的重启限度, 默认为1
、5
,表示5
s内最多重启1
次, 超过这个限度后,supervisor
进程将会终止所有子进程和它本身。能够有效地防止supervisor
无限重启。注意,Intensity=2
、Period=10
与Intensity=1
、Period=5
是不同的,{1,5}
短时间内不允许重启2次而{2,10}
是允许的。
ChildSpec
定义了如何启动和管理子进程, 一般是元组:{Id, Start, Restart, Shutdown, Type, Modules}
#{id => Id, % mandatory
start => {M,F,A}}, % mandatory
restart => permanet|temporary|transient, % optional
shutdown => brutal_kill|TimeOut, % optional
type => supervisor|worker, % optional
modules => modules()} % optional
Id
为子进程的标识符;
Start
是一个{M,F,A}
元组,用于启动子进程;
Restart
是指定了子进程终止时的处理策略,permanent
参数表示每次终止都重启,temporary
表示不再重启(即使因为其他进程终止导致本进程终止而后需要重启的情况),transient
表示只有当进程被意外终止才会重启,正常原因包括normal
, shutdown
, or {shutdown,Term}
; 默认为permanent
;
Shutdown
指定子进程的终止方式,brutal_kill
表示通过调用exit(ChildPid, kill)
终止子进程,TimeOut
表示表示通过调用exit(ChildPid, shutdown)
终止子进程,此时将会等待TimeOut
ms等待子进程返回一个理由为shutdown
的退出信号。当子进程为supervisor
进程时,应该设置成infinity
。默认为50000;
Type
指定子进程的类型,默认worker
;
Module
指定回调模块,默认为{M,F,A}
中的M
。
注意,在init
返回后,supervisor
会根据子进程列表[ChildSpec]
,启动所有的子进程。supervisor:start_link
是同步调用,在所有子进程启动之前它不会返回。
supervisor函数库
supervisor:check_childspecs([ChildSpec])
检查ChildSpec
是否合法。
supervisor:count_children(SupRef)
获取supervisor
进程的子进程情况。
supervisor:start_child(SupRef, ChildSpec)
动态启动一个子进程,不过该子进程在supervisor
进程重启后将会丢失。
supervisor:terminate_child(SupRef, Id)
终止一个子进程,无论该子进程是静态添加抑或是动态添加的。此处的Id
,如果是动态添加的则必须为子进程Pid
。
supervisor:restart_child(SupRef, Id)
重启一个子进程。此子进程必须在ChildSpecs
中。
supervisor:delete_child(SupRef, Id)
删除一个子进程,并从ChildSpecs
中删除。
使用
%% one_for_one Supervisors
-module(ch_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init(_Args) ->
SupFlags = {one_for_one,1,5},
ChildSpecs = [#{id => ch3,
start => {ch3, start_link, []},
restart => permanent,
shutdown => brutal_kill,
type => worker,
modules => [ch3]}],
{ok, {SupFlags, ChildSpecs}}.
%% Simplified one_for_one Supervisors
-module(simple_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link(local, ?MODULE}, ?MODULE, []).
init(_Args) ->
SupFlags = {simple_one_for_one, 0,1},
ChildSpecs = [#{id => ch4, start => {ch4, start_link, []}, restart => permanent,
shutdown => brutal_kill, type => worker, modules => [ch4]}}],
{ok, {SupFlags, ChildSpecs}}.
对于Simplified one_for_one Supervisors
而言, 所有的子进程都是通过supervisor:start_child(Sup, [List])
动态添加的,相当于调用apply(call, start_link, []++[List])
即call:start_link([List])
。子进程通过supervisor:terminate_child(SupRef, Pid)
终止。
需要注意的是,Simplified one_for_one Supervisors
子进程的终止是异步的,终止顺序不可预测。
gen_statem
通用状态机
gen_statem
是Erlang/OTP 19.0
引入的新behavior。用于替换gen_fsm
。
gen_statem支持2种回调模式:
-
state_functions
要求状态名StateName必须是原子,并且回调函数 的名称与状态 同名。这一点与gen_fsm
一样。Module:StateName(enter, OldState, Data) -> StateEnterResult(StateName) Module:StateName(EventType, EventContent, Data) -> StateFunctionResult
-
handle_event_function
状态名可以是任何形式,此时回调函数为Module:handle_event/4
,适用于所有状态下。Module:handle_event(enter, OldState, StateName, Data) -> StateEnterResult(StateName) Module:handle_event(EventType, EventContent, StateName, Data) -> HandleEventResult
**进入状态回调返回值: **
回调函数Module:callback_mode()
指定gen_statem
的回调模式。其返回值为state_functions
|handle_event_function
|[state_functions, state_enter]
|[handle_event_function, state_enter]
。
在callback_mode/0
函数的返回列表
中加入state_enter
,会在每次状态改变的时候调用回调函数,参数为(enter, OldState, ...)
,即进入状态回调。
StateEnterResult(StateName) ->
{next_state, StateName, NewData} |
{next_state, StateName, NewData, Actions} |
{keep_state, NewData} |
{keep_state, NewData, Actions} |
keep_state_and_data |
{keep_state_and_data, Actions} |
{repeat_state, NewData} |
{repeat_state, NewData, Actions} |
repeat_state_and_data |
{repeat_state_and_data, Actions}
其中,Actions
表示状态迁移动作,在回调函数返回后指定gen_statem
去执行,主要包含:
[postpone |
{postpone, true | false} |
{next_event, EventType :: event_type(), EventContent :: term()} |
hibernate |
{hibernate, true | false} |
Timeout |
{timeout, Time, EventContent} |
{timeout, Time, EventContent, Options} |
{{timeout, Name}, Time, EventContent} |
{{timeout, Name}, Time, EventContent, Options} |
{state_timeout, Time, EventContent} |
{state_timeout, Time, EventContent, Options} |
{reply, From, Reply}]
其中,
-
postpone
表示在当前状态下延缓事件,等待状态改变时再重新触发; -
hibernate
表示挂起gen_statem
进程直至下一个事件到来; -
TimeOut
、{timeout, Time, EventContent}
、{timeout, Time, EventContent, Options}
表示事件超时,一定时间内没有事件达到就会触发该超时。事件超时的定时器在有事件达到的时候就会被取消。超时后会触发事件timeout
; -
{{timeout, Name}, Time, EventContent}
、{{timeout, Name}, Time, EventContent, Options}
表示一般超时,一定时间后触发,与状态改变、事件更新无关。超时后会触发事件{timeout, Name}
。可以通过设置一个同名的超时动作来重启这个定时器,如果想要终止这个定时器只需要将超时改成infinity
即可; -
{state_timeout, Time, EventContent}
、{state_timeout, Time, EventContent, Options}
表示状态超时,一定时间内状态没有变化就会触发该超时。状态超时会在状态改变时被取消。超时后会触发事件state_timeout
; -
{next_event, EventType, EventContent :: term()}
表示像事件队列头部插入一个事件,该事件将会被优先处理,可用于触发所有事件。{next_event, internal, EventContent}
触发的事件为internal
,表示内部事件,用于区分外部事件;EventType
事件类型包含:{call, From} | cast | info | timeout | {timeout, Name} | state_timeout | internal
{call, From}
、cast
、info
这3种事件是通过接口产生的外部事件,其他的则是在执行过程中由gen_statem
产生的。cast
由gen_statem:cast(ServerRef, Msg)
生成,其中Msg
会传递给参数EventContent
。{call,From}
由gen_statem:call(ServerRef, Request)
生成,其中Request
会传递给参数EventContent
。From
是通过迁移动作{reply,From,Reply}
或在回调模块中调用gen_statem:reply(From, Reply)
进行回复时的回复地址。info
由任何发送到gen_statem
进程的普通消息生成。进程消息会传递给参数EventContent
。state_timeout
由迁移动作{state_timeout,Time,EventContent}
状态定时器超时生成。{timeout,Name}
由迁移动作{{timeout,Name},Time,EventContent}
一般定时器超时生成。timeout
由迁移动作{timeout,Time,EventContent}
(或者它的缩写形式Time
)事件定时器超时生成。internal
由迁移动作{next_event,internal,EventContent}
生成。上面所有事件类型都可以通过使用next_event
动作:{next_event,EventType,EventContent}
生成。
-
{reply, From, Reply}
用于给From
返回值。除此之后也可以通过调用gen_statem:reply(From, Reply)
进行返回。
需要注意的是,在Actions
列表中的后面的Action
是会覆盖前面的同类型的Action
,例如,列表中的所有事件超时只有最后一个会生效。
1. behavior函数和回调函数之间的关系如下:
gen_statem module Callback module desc
----------------- --------------- ----------------
gen_statem:start 启动gen_statem状态机进程
gen_statem:start_link -----> Module:init/1 启动并链接状态机进程
Server start or code change
-----> Module:callback_mode/0 回调模式
gen_statem:stop -----> Module:terminate/3
gen_statem:call
gen_statem:cast
erlang:send
erlang:'!' -----> Module:StateName/3, Module:handle_event/3 <- (state_functions) 状态处理
Module:handle_event/4 <- (handle_event_function)
- -----> Module:terminate/3
- -----> Module:code_change/4
2. gen_statem使用
- 回调模式为
state_function
-module(pushbutton).
-behaviour(gen_statem).
-export([start/0,push/0,get_count/0,stop/0]).
-export([terminate/3,code_change/4,init/1,callback_mode/0]).
-export([on/3,off/3]).
name() -> pushbutton_statem.
% The registered server name
%% API. This example uses a registered name name()
%% and does not link to the caller.
start() ->
gen_statem:start({local,name()}, ?MODULE, [], []).
push() ->
gen_statem:call(name(), push).
get_count() ->
gen_statem:call(name(), get_count).
stop() ->
gen_statem:stop(name()).
%% Mandatory callback functions
terminate(_Reason, _State, _Data) ->
void.
code_change(_Vsn, State, Data, _Extra) ->
{ok,State,Data}.
init([]) ->
%% Set the initial state + data. Data is used only as a counter.
State = off, Data = 0,
{ok,State,Data}.
callback_mode() ->
state_functions.
%%% state callback(s)
off({call,From}, push, Data) ->
%% Go to 'on', increment count and reply
%% that the resulting status is 'on'
{next_state,on,Data+1,[{reply,From,on}]};
off(EventType, EventContent, Data) ->
handle_event(EventType, EventContent, Data).
on({call,From}, push, Data) ->
%% Go to 'off' and reply that the resulting status is 'off'
{next_state,off,Data,[{reply,From,off}]};
on(EventType, EventContent, Data) ->
handle_event(EventType, EventContent, Data).
%% Handle events common to all states
handle_event({call,From}, get_count, Data) ->
%% Reply with the current count
{keep_state,Data,[{reply,From,Data}]};
handle_event(_, _, Data) ->
%% Ignore all other events
{keep_state,Data}.
- 回调模式为
handle_event_function
-module(pushbutton).
-behaviour(gen_statem).
-export([start/0,push/0,get_count/0,stop/0]).
-export([terminate/3,code_change/4,init/1,callback_mode/0]).
-export([on/3,off/3]).
name() -> pushbutton_statem.
% The registered server name
%% API. This example uses a registered name name()
%% and does not link to the caller.
start() ->
gen_statem:start({local,name()}, ?MODULE, [], []).
push() ->
gen_statem:call(name(), push).
get_count() ->
gen_statem:call(name(), get_count).
stop() ->
gen_statem:stop(name()).
%% Mandatory callback functions
terminate(_Reason, _State, _Data) ->
void.
code_change(_Vsn, State, Data, _Extra) ->
{ok,State,Data}.
init([]) ->
%% Set the initial state + data. Data is used only as a counter.
State = off, Data = 0,
{ok,State,Data}.
callback_mode() ->
handle_event_function.
%%% state callback(s)
handle_event({call,From}, push, off, Data) ->
%% Go to 'on', increment count and reply
%% that the resulting status is 'on'
{next_state,on,Data+1,[{reply,From,on}]};
handle_event({call,From}, push, on, Data) ->
%% Go to 'off' and reply that the resulting status is 'off'
{next_state,off,Data,[{reply,From,off}]};
%% %% Event handling common to all states
handle_event({call,From}, get_count, State, Data) ->
%% Reply with the current count
{next_state,State,Data,[{reply,From,Data}]};
handle_event(_, _, State, Data) ->
%% Ignore all other events
{next_state,State,Data}.