Eric's 博客--游戏程序员

优秀的代码是艺术品,它需要精雕细琢!

  :: 首页 :: 博问 :: 闪存 :: 新随笔 :: :: :: 管理 ::

1.简介

Gen_fsm是一个通用的有限状态机,它描述了这样的一组关系:
State(S) x Event(E) -> Actions(A),State(S')
这个关系意味着:如果在S状态下发生事件E,将执行动作A并返回状态S'.对于一个FSM实现可以使用gen_fsm行为来实现,它提供了标准的接口函数和回调函数。并且,gen_fsm进程可以安装在supervisor监控树中。回调函数与导出函数的关系如下:
  1.  1 gen_fsm moduleCallbackmodule
     2 -----------------------------
     3 gen_fsm:start_link ----->Module:init/1
     4 gen_fsm:send_event ----->Module:StateName/2
     5 gen_fsm:send_all_state_event ----->Module:handle_event/3
     6 gen_fsm:sync_send_event ----->Module:StateName/3
     7 gen_fsm:sync_send_all_state_event ----->Module:handle_sync_event/4
     8 ------>Module:handle_info/3
     9 ------>Module:terminate/3
    10 ------>Module:code_change/4
如果回调函数失败或返回坏的值,gen_fsm将会终止。
gen_fsm处理系统消息记录记录在sys模块中(sys模块可以用来调试gen_fsm)。
注意:gen_fsm不会主动的捕获退出信号,必须在回调函数初始化时被指定。process_flag(trap_exit, true).
如果指定的gen_fsm不存在或给与的参数不正确,模块里的所有函数将失败。
如果回调函数返回'hibernate',gen_fsm进程将进入hibernate状态。如果服务器长时间处于空闲状态这可能非常有用。然而应该非常小心的使用这个特征,因为它意味着至少有垃圾回收器,当调用一个忙碌的状态机时,你不能做你想做的事情(会因为垃圾回收而导致状态机停滞)。

2.函数

2.1导出函数
start_link(Module, Args, Options) -> Result
start_link(FsmName, Module, Args, Options) -> Result
FsmName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}
Options = [Option]
 Option = {debug,Dbgs} | {timeout,Time} | {spawn_opt,SOpts}
  Dbgs = [Dbg]
   Dbg = trace | log | statistics
    | {log_to_file,FileName} | {install,{Func,FuncState}}
  SOpts = [SOpt]
   SOpt - see erlang:spawn_opt/2,3,4,5
Result = {ok,Pid} | ignore | {error,Error}
 Error = {already_started,Pid} | term()
用于创建一个gen_fsm进程,可以作为监控树的一部分。可以被监控树直接或间接的调用,将确保gen_fsm被链接到监控树。gen_fsm进程调用Module:init/1来初始化。为了确保同步的启动gen_fsm,start_link/3,4直到Module:init/1返回时才返回。
如果FsmName = {local,Name},gen_fsm在本地通过register/2注册为Name.如果FsmName = {global, GlobalName},gen_fsm在全局通过global:register_name/2注册为GlobalName.如果FsmNam = {via, Module, ViaName},gen_fsm通过Module注册为ViaName,同时回调模块Module将导出函数register_name/2, unregister_name/2, whereis_name/1和send/2,其原理同global,因此{via, Module, GlobalName}是一个有效的引用。
如果没有指定name,gen_fsm不被注册。
Args是一个任意的项式,用于作为Module:init/1的参数。
如果option是{timeout, Time}那么gen_fsm有Time毫秒进行初始化,否则,将返回{error, timeout}来终止。
如果option是{debug, Dbgs}那么Dbgs中相应的sys函数将被调用。详情见sys(3).
如果option是{spawn_opt, Sopts}那么通过指定的值列表来产生gen_fsm进程。详见erlang:spawn_opt
  1. link
  2. |{priority, priority_level()}
  3. |{min_heap_size, integer()>=0}
  4. |{min_bin_vheap_size, integer()>=0}
  5. |{fullsweep_after, integer()>=0}
如果gen_fsm被成功的创建和初始化,将返回{ok,Pid},如果已经存在指定FsName的进程,将返回{error,{alredy_started, Pid}}.
如果Module:init/1因为Reason失败,将返回{error,Reason}.如果Module:init/1返回{stop, Reason}或ignore,并且相应的返回{error,Reason}或ignore.
start(Module, Args, Options) -> Result
start(FsmName, Module, Args, Options) -> Result
FsmName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}
Options = [Option]
 Option = {debug,Dbgs} | {timeout,Time} | {spawn_opt,SOpts}
  Dbgs = [Dbg]
   Dbg = trace | log | statistics
    | {log_to_file,FileName} | {install,{Func,FuncState}}
  SOpts = [term()]
Result = {ok,Pid} | ignore | {error,Error}
 Error = {already_started,Pid} | term()
用于创建一个独立gen_fsm进程,并不是监控树的一部分。参数描述参见start_link/3,4.
send_event(FsmRef, Event) -> ok
FsmRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
向gen_fsm的FsmRef发送一个异步事件,并立即返回ok.gen_fsm将调用Module:StateName/2来处理事件,StateName为当前gen_fsm的状态。
FsmRef可能是:
  • Pid
  • Name,本地注册的gen_fsm
  • {Name,Node}另一个节点上本地注册gen_fsm
  • {global,GlobalName}全局注册的gen_fsm
  • {via,Module,ViaName}通过指定的进程注册的gen_fsm
Event是一个任意的项式,将被作为参数传递给Module:StateName/2处理。
send_all_state_event(FsmRef, Event) -> ok
FsmRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
向gen_fsm的FsmRef发送一个异步事件并立即返回ok.这个gen_fsm将调用Module:handle_event/3来处理这个事件。
参数描述参见send_event/2.
send_event和send_all_state_event的不同是处理事件的回调函数不同,该函数是在任何状态下都以同样的方式处理事件,只有一个handle_event子句来处理事件。
sync_send_event(FsmRef, Event) -> Reply
sync_send_event(FsmRef, Event, Timeout) -> Reply
FsmRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
Timeout = int()>0 | infinity
Reply = term()
向gen_fsm的FsmRef发送一个事件然后等待直到reply返回或发生超时,将调用Module:StateName/3来处理事件,StateName是gen_fsm当前的状态名。
参数描述参见send_event/2.
Timeout是一个等待reply返回大于0指定的毫秒数,或指定为infnity.默认是5000毫秒。如果指定时间内未返回reply函数调用失败。
返回值Reply是回调函数Module:StateName/3返回的。
sync_send_all_state_event(FsmRef, Event) -> Reply
sync_send_all_state_event(FsmRef, Event, Timeout) -> Reply
FsmRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
Timeout = int()>0 | infinity
向gen_fsm的FsmRef发送一个事件然后等待reply的返回或超时。gen_fsm将调用Module:handle_sync_event/4来处理。
参数描述参见send_event/2和sync_send_event/3.
reply(Caller, Reply) -> true
Caller - see below
该函数用于通过gen_fsm向指定的客户端发送返回值,例如sync_send_event/2,3或sync_send_all_state/2,3当其回调函数Module:StateName/3或Module:handle_sync_event/4不能返回值时。
Caller是客户端的From,Reply是一个任意的项式,他将返回给sync_send_event/2,3或sync_send_all_state/2,3.
send_event_after(Time, Event) -> Ref
Time = integer()
Ref = reference()
用于在gen_fsm内部发送一个延迟事件,在Time时间后将触发。立即返回一个引用,它可以用cancel_timer/1来取消该延迟事件。
gen_fsm将调用Module:StateName/2处理该事件,StateName是当gen_fsm在延迟时间到时延迟事件被发送时的状态名字。
Event是任意类型的项式将作为参数传递给Module:StateName/2.
start_timer(Time, Msg) -> Ref
Time = integer()
Ref = reference()
在gen_fsm内部发送一个超时的事件,在Time事件时执行,并立刻返回他的引用,可以通过cancel_timer/1来取消该方法。
gen_fsm将调用Module:StateName/2处理该事件,StateName是发送该超时事件的状态。
Msg是任意类型项式的超时信息{timeout, Ref, Msg},将作为Module:StateName/2的参数信息。
cancel_timer(Ref) -> RemainingTime | false
Ref = reference()
RemainingTime = integer()
gen_fsm调用该函数来取消名称为Ref的内部定时器。
引用Ref是由send_event_after/2和start_timer/2返回。
如果定时器已经超时,但是并没用事件发送,他被作为超时事件被取消,因此该函数的返回不会有错误的计时器事件。
如果Ref指定的是活跃的事件计时器,将返回剩余的时间直到计时器终止前,否则返回false.
enter_loop(Module, Options, StateName, StateData)
enter_loop(Module, Options, StateName, StateData, FsmName)
enter_loop(Module, Options, StateName, StateData, Timeout)
enter_loop(Module, Options, StateName, StateData, FsmName, Timeout)
Options = [Option]
 Option = {debug,Dbgs}
  Dbgs = [Dbg]
   Dbg = trace | log | statistics
    | {log_to_file,FileName} | {install,{Func,FuncState}}
FsmName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}
Timeout = int() | infinity
使一个存在的进程变成gen_fsm.没有返回,取而代之的是,调用进程将进入gen_fsm的接受循环成为一个gen_fsm进程。进程必须使用proc_lib来启动该函数.因此通过该函数进行初始化和名字注册。
当要进行一个比gen_fsm更要复杂的初始化过程时,这个函数非常有用。
Module, Options and FsmName与start_link中的意义一样。然而,如果FsmName被指定,在这个函数调用前,这个进程必须被相应地注册。
StateName,StateDate and Timeout的意义与Module:init/1一样。同时,回调模块Module不需要导出init/1方法。
失败情况:通过proc_lib启动函数不能启动该进程时;不能注册指定的FsmName时。
2.2回调函数
Module:init(Args) -> Result
Result = {ok,StateName,StateData} | {ok,StateName,StateData,Timeout}
  | {ok,StateName,StateData,hibernate}
  | {stop,Reason} | ignore
 Timeout = int()>0 | infinity
每当gen_fsm使用gen_fsm:start[_link]/3,4开始时,通过新进程调用该函数来初始化。
Args是启动函数提供的参数。
如果初始化成功,这个函数应该返回{ok, StateName, SateData[,Timeout | hibernate]},SateName是初始的状态名,StateData是初始的状态数据。
如果提供了一个整数的超时值,在Timeout时间内如果没有接受到事件或消息那么超时将发生。超时的表现是通过一个原子timeout被回调函数Modules:StateName/2处理。其默认值是infinity,表示会一直等待下去。
如果hibernate被指定,那么进程将进入hibernate状态等待下一个消息的到达。
如果在初始化期间发生了一些错误,函数应该返回{stop, Reason}或ignore.
Module:StateName(Event, StateData) -> Result
Event = timeout | term()
Result = {next_state,NextStateName,NewStateData} 
  | {next_state,NextStateName,NewStateData,Timeout}
  | {next_state,NextStateName,NewStateData,hibernate}
  | {stop,Reason,NewStateData}
 Timeout = int()>0 | infinity
这是一个每个可能状态名的一个函数实例。每当gen_fsm接收到由gen_fsm:send_event/2发送过来的事件时,当前状态名StateName的函数实例将被调用来处理这个事件。同时他也处理超时事件。
Event要么是超时发生时的原子timeout,或send_event/2传递过来的。
如果该函数返回{next_state, NextStateName, NewStateData [, Timeout | hibernate]}时,gen_fsm将继续执行同时转变到新的状态NextStateName并更新数据NewStateDate.hibernate和Timeout的描述详见Module:init/1.
如果返回{stop, Reason, NewStateName},gen_fsm将调用Module:terminate(Reason, NewStateData)终止。
Module:handle_event(Event, StateName, StateData) -> Result
Result = {next_state,NextStateName,NewStateData} 
  | {next_state,NextStateName,NewStateData,Timeout}
  | {next_state,NextStateName,NewStateData,hibernate}
  | {stop,Reason,NewStateData}
 Timeout = int()>0 | infinity
每当gen_fsm接收到由gen_fsm:send_all_state_event/2发送过来的事件时,这个函数将被调用来处理该事件。
参数描述以及返回值描述参见Module:StateName/2.
Module:StateName(Event, From, StateData) -> Result
From = {pid(),Tag}
Result = {reply,Reply,NextStateName,NewStateData}
  | {reply,Reply,NextStateName,NewStateData,Timeout}
  | {reply,Reply,NextStateName,NewStateData,hibernate}
  | {next_state,NextStateName,NewStateData}
  | {next_state,NextStateName,NewStateData,Timeout}
  | {next_state,NextStateName,NewStateData,hibernate}
  | {stop,Reason,Reply,NewStateData} | {stop,Reason,NewStateData}
 Timeout = int()>0 | infinity
 Reason = normal | term()
这是一个每个可能状态名的一个函数实例。每当gen_fsm接收到由gen_fsm:sync_send_event/2发送过来的事件时,当前状态名StateName的函数实例将被调用来处理这个事件。
Event是由sync_send_event的Event参数提供。
From是一个元组{Pid, Tag},Pid是调用sync_send_event/2,3的进程的Pid,Tag是他的唯一标识。
当函数返回{reply, Reply, NextStateName, NewStateName [, Timeout | hibernate]}时,gen_fsm将继续执行同时转变到状态NextStateName,并跟新值到NewStateData.Timeout和hibernate的描述参见Module:init/1.
当函数返回{next_state, NextStateName, NewStateData [, Timeout | hibernate]}时,gen_fsm将继续执行,同时转变到状态NextStateName并更数据到NewStateName.同时必须现实的调用gen_fsm:reply/2来返回数据reply给From.
如果函数返回{stop, Reason, Reply, NewStateData}或{stop, Reason, NewStateNameData}(该情况必须明确的使用gen_fsm:reply/2返回reply)时,gen_fsm将调用Module:terminate(Reason NewStateName)终止。
Module:handle_sync_event(Event, From, StateName, StateData) -> Result
Result = {reply,Reply,NextStateName,NewStateData}
  | {reply,Reply,NextStateName,NewStateData,Timeout}
  | {reply,Reply,NextStateName,NewStateData,hibernate}
  | {next_state,NextStateName,NewStateData}
  | {next_state,NextStateName,NewStateData,Timeout}
  | {next_state,NextStateName,NewStateData,hibernate}
  | {stop,Reason,Reply,NewStateData} | {stop,Reason,NewStateData}
 Timeout = int()>0 | infinity
每当gen_fsm接受到由gen_fsm:sync_send_all_state_event/2,3发送过来的事件时,这个函数被调用来处理该事件。
参数详见Module:StateName/3描述。
Module:handle_info(Info, StateName, StateData) -> Result
Result = {next_state,NextStateName,NewStateData}
  | {next_state,NextStateName,NewStateData,Timeout}
  | {next_state,NextStateName,NewStateData,hibernate}
  | {stop,Reason,NewStateData}
当gen_fsm接收到其他消息时(除出去同步或异步事件以及系统消息),该函数被调用来处理该消息。
其他参数以及返回值描述参见Module:StateName/2.
Module:terminate(Reason, StateName, StateData)
Reason = normal | shutdown | {shutdown,term()} | term()
这个函数在gen_fsm终止时被调用。它的作用与Module:init/1相对并可以做一些必要的清理。当他返回gen_fsm因为Reason终止的原因,返回值将被忽略。
如果Reason是term指定的停止原因,StateName就是当前状态,StateData就是当前状态数据。
如果Reason取决于gen_fsm为什么被终止。如果他是因为另一个回调函数返回了stop元组{stop,...},Reason将在那个元组里被指定。如果由于失败导致,Reason是一个错误原因。
如果gen_fsm是监控树的一部分,被他的监控树所终止,Reason = shutdown 要满足以下条件:
  • gen_fsm被设置成可捕获退出信号
  • 监控树的关闭策略是指定Timeout而不是brutal_kill
甚至如果gen_fsm并不是监控树的一部分,如果从其父进程接收到'EXIT'信号这个函数也会被调用,Reason就是'EXIT'信息。
否则,gen_fsm将立即终止。
注意:非normalshutdown, or {shutdown,Term}的任何其他原因,如gen_fsm的终止是因为error,这些错误将由error_logger:format/2进行报告。 
Module:code_change(OldVsn, StateName, StateData, Extra) -> {ok, NextStateName, NewStateData}
OldVsn = Vsn | {down, Vsn}
关于代码的热更新,后续专题会继续介绍。

3.实践

3.1 启动一个gen_fsm
  1.  1 start_link(Code)->
     2 %%gen_fsm:start_link({local,?SERVER},?MODULE, lists:reverse(Code),[]).
     3 gen_fsm:start({local,?SERVER},?MODULE, lists:reverse(Code),[{timeout,1000}]).
     4 -spec(init(Args:: term())->
     5 {ok,StateName:: atom(),StateData::#state{}} |
     6 {ok,StateName:: atom(),StateData::#state{}, timeout() | hibernate} |
     7 {stop,Reason:: term()}| ignore).
     8 init(Code)->
     9 %%timer:sleep(2000),
    10 %%{stop,Code}.
    11 %%ignore.
    12 {ok, unlock,{[],Code}}.
当调用gen_fsm:start开启fsm服务器事,将回调Modules:init/1进行初始化,并初始化状态unlock,以及状态值{[],Code}.
3.2 发送一个事件
  1.  1 button(Digital)->
     2 gen_fsm:send_event(?SERVER,{button,Digital}).
     3 unlock({button,Digital},{SoFar,Code})->
     4 case[Digital|SoFar] of
     5 Code->
     6 io:format("Pass OK!~n"),
     7 do_unlock(),
     8 {next_state, open,{[],Code},3000};
     9 InCompletwhen length(InComplet)< length(Code)->
    10 io:format("Input:~p,InComplet~p,Code:~p~n",[Digital,[Digital|SoFar],Code]),
    11 {next_state, unlock,{InComplet,Code}};
    12 _Worng->
    13 io:format("ReInput Password!~n"),
    14 {next_state, unlock,{[],Code}}
    15 end.
    16 open(timeout,State)->
    17 do_lock(),
    18 timer:sleep(1000),
    19 %%{stop, normal,State}.
    20 {next_state, unlock,State}.
发送一个异步事件,然后当前状态对事件进行处理,根据一定的条件进行状态变更,返回{stop, Reason, State}将调用Module:terminate/2进行终止.
  1.  1 sync_button()->
     2 gen_fsm:sync_send_event(?SERVER,open).
     3 %%gen_fsm:sync_send_event(?SERVER,close,1000).
     4 
     5 unlock(open,_From,State)->
     6 do_unlock(),
     7 io:format("~p~p~n",[_From,State]),
     8 gen_fsm:reply(_From,{ok,"Successfly!"}),
     9 %%gen_fsm:reply(_From,normal),{stop, normal,State}.
    10 %%{stop, normal, normal,State}.
    11 {next_state, close,State,1000}.
    12 %%Reply= ok,
    13 %%{reply,Reply, open,State,3000}.
    14 close(timeout ,State)->
    15 do_lock(),
    16 io:format("~p~p~n",["timeout",State]),
    17 {next_state, open,State,1000}.
发送一个同步事件,当前状态进行处理以后等待返回,返回{reply, Reply, StateName, State},Reply将返回给调用端。返回{next_state, State, State},那么必须在返回之前调用gen_fsm:reply/2显示的返回给调用端。
3.3 发送一个所有状态都能接受的事件
  1.  1 send(Data)->
     2 gen_fsm:send_all_state_event(?SERVER,{send,Data}).
     3 -spec(handle_event(Event:: term(),StateName:: atom(),
     4 StateData::#state{}) ->
     5 {next_state,NextStateName:: atom(),NewStateData::#state{}} |
     6 {next_state,NextStateName:: atom(),NewStateData::#state{},
     7 timeout()| hibernate}|
     8 {stop,Reason:: term(),NewStateData::#state{}}).
     9 handle_event({send,Data},StateName,State)->
    10 io:format("Send Data:~p,StateName:~p,State,~p~n",[Data,StateName,State]),
    11 {next_state,StateName,State}.
向所有的状态发送一个异步的事件,并由handle_event/3进行处理。
  1. 1 sync_send(Data)->
    2 Res= gen_fsm:sync_send_all_state_event(?SERVER,{send,Data}),
    3 io:format("~p~n",[Res]).
    4 handle_sync_event({send,Data},_From,StateName,State)->
    5 timer:sleep(2000),
    6 gen_fsm:reply(_From,{Data,StateName,State,_From}),
    7 {next_state, open,State,1000}.
    8 %%{reply,{Data,StateName,State,_From}, open,State,1000}.
    9 %%{reply,{Data,StateName,State,_From},StateName,State}.
向所有的状态发送一个同步事件,并由handle_sync_event/4进行处理,并向调用端返回Reply,如果返回{next_state, StateName, State}必须显示调用gen_fsm:reply/2向调用端返回。
3.4 开启一个定时器
  1.  1 unlock(open,_From,State)->
     2 do_unlock(),
     3 %%gen_fsm:send_event_after(2500,{button,48}),
     4 Ref= gen_fsm:start_timer(1500,"start_timer"),
     5 io:format("~p~p~n",[_From,State]),
     6 gen_fsm:reply(_From,{ok,"Successfly!"}),
     7 proc_lib:spawn(fun()->
     8 timer:sleep(2000),
     9 Time= gen_fsm:cancel_timer(Ref),
    10 io:format("RemainTime:~p~n",[Time])
    11 end),
    12 %%gen_fsm:reply(_From,normal),{stop, normal,State}.
    13 %%{stop, normal, normal,State}.
    14 {next_state, close,State,1000}.
    15 %%Reply= ok,
    16 %%{reply,Reply, open,State,3000}.
send_event_after/2表示在指定时间后发送事件,start_timer/2表示在指定事件后发送一个超时事件{timeout, Ref, Msg},但同时可以用cancel_timer/1来结束定时器。
3.5 将普通进程转变为gen_fsm进程
  1.  1 -spec(start_link()->{ok, pid()}| ignore |{error,Reason:: term()}).
     2 start_link()->
     3 io:format("start_link~n"),
     4 proc_lib:start_link(?MODULE, start_loop,[self()]).
     5 %%gen_fsm:start_link({local,?SERVER},?MODULE,[],[]).
     6 start_loop(Person)->
     7 io:format("start_loop~n"),
     8 proc_lib:init_ack(Person,self()),
     9 register(?MODULE,self()),
    10 gen_fsm:enter_loop(?MODULE,[], state_name,#state{}, {local, ?MODULE}).
通过gen_fsm:enter_loop将一个普通的进程转变为gen_fsm进程。同时不必导出Module:init/1回调函数。适用于比init:/1要更复杂的初始化的处理。

4.总结

gen_fsm实现了通用的有限状态机(FSM),对与在多种状态间的变更处理非常的有用,比如文本解析,模式匹配、游戏逻辑等等方面的处理都是它的强项,所以这个behaviour非常之重要,gen_fsm提供了简便的接口与回调函数来构建有限状态机,根据不同的返回参数在不同的状态间进行切换,也具备了同步或异步事件的处理。
 
 
优秀的代码是艺术品,它需要精雕细琢!





posted on 2015-07-27 12:35  EricLiuw  阅读(2580)  评论(0编辑  收藏  举报