Gen_Fsm Behaviour

1,有限状态机 
FSM,有限状态机,可以用以下形式来描述做一个关系集: 
Java代码  
  1. State(S) x Event(E) -> Actions(A), State(S')  

意思是,如果我们位于状态S,然后事件E发送了,则我们应该执行动作A,并且将状态改为S' 

2,例子 
有一个代码锁的门,如果输入的代码顺序是对的,那么将门打开30秒 
如果输入代码不完全,则等待下次按钮按下,如果输入代码顺序是错的,则重新开始等待按钮按下 
Java代码  
  1. -module(code_lock).  
  2. -behaviour(gen_fsm).  
  3.   
  4. -export([start_link/1]).  
  5. -export([button/1]).  
  6. -export([init/1, locked/2, open/2]).  
  7.   
  8. start_link(Code) ->  
  9.   gen_fsm:start_link({local, code_lock}, code_lock, Code, []).  
  10.   
  11. button(Digit) ->  
  12.   gen_fsm:send_event(code_lock, {button, Digit}).  
  13.   
  14. init(Code) ->  
  15.   {ok, locked, {[], Code}}.  
  16.   
  17. locked({button, Digit}, {SoFar, Code}) ->  
  18.   case [Digit|SoFar] of  
  19.     Code ->  
  20.       do_unlock(),  
  21.       {next_state, open, {[], Code}, 3000};  
  22.       Incomplete when length(Incomplete) < length(Code) ->  
  23.         {next_state, locked, {Incomplete, Code}};  
  24.       _Wrong ->  
  25.         {next)state, locked, {[], Code}};  
  26.   end.  
  27.   
  28. open(timeout, State) ->  
  29.   do_lock(),  
  30.   {next_state, locked, State}.  


3,启动Gen_Fsm 
上面的例子里,使用code_lock:start_link(Code)启动gen_fsm: 
Java代码  收藏代码
  1. start_link(Code) ->  
  2.   gen_fsm:start_link({locak, code_lock}, code_lock, Code, []).  

start_link调用gen_fsm:start_link/4,启动一个新的gen_fsm进程并连接。 
1)第一个参数{local, code_lock}指定名字,在本地注册为code_lock 
2)第二个参数code_lock执行callback模块 
3)第三个参数Code是传递给callback方法init的参数,在这里init方法得到正确的代码锁的代码 
4)第四个采纳数[]是options 
如果进程注册成功,则新的gen_fsm进程调用callback方法code_lock:init(Code),返回{ok, StateName, StateData} 
StateName是gen_fsm的初始状态,在这里返回的是locked,表示初始状态下门是锁着的 
StateData是gen_fsm的内部状态,在这里state data是当前的按钮顺序(初始时为空)和正确的锁代码 
Java代码  收藏代码
  1. init(Code) ->  
  2.   {ok, locked, {[], Code}}.  

注意gen_fsm:start_link是同步的,直到gen_fsm进程初始化并准备好开始接受请求时才会返回 
gen_fsm:start_link表示有supervisor,gen_fsm:start则是启动单独的gen_fsm进程 

4,事件通知 
使用gen_fsm:send_event/2来实现按钮事件的通知: 
Java代码  收藏代码
  1. button(Digit) ->  
  2.   gen_fsm:send_event(code_lock, {button, Digit}).  

code_lock是gen_fsm进程的名字,{button, Digit}是真正的事件 
事件会当成消息发送给gen_fsm进程,事件接收以后,gen_fsm调用StateName(Event, StateData)并返回{next_state, StateName1, StateData1} 
StateName是当前状态的名字,StateName1是下一个即将变成的状态的名字,StateData1是gen_fsm的新的state data值: 
Java代码  收藏代码
  1. locked({button, Digit}, {SoFar, Code}) ->  
  2.   case [Digit|SoFar] of  
  3.     Code ->  
  4.       do_unlock(),  
  5.       {next_state, open, {[], Code}, 3000};  
  6.     Incomplete when length(Incomplete) < length(Code) ->  
  7.       {next_state, locked, {Incomplete, Code}};  
  8.     _Wrong ->  
  9.       {next_state, locked, {[], Code}};  
  10.   end.  
  11.   
  12. open(timeout, State) ->  
  13.   do_lock(),  
  14.   {next_state, locked, State}.  

如果门是锁着的,这时按下一个按钮,则比较目前按钮的顺序和正确的代码顺序,由比较结果来决定是unlock门还是继续保持locked  

5,超时 
当输入正确的按钮顺序后,门unlock然后返回如下的tuple: 
Java代码  收藏代码
  1. {next_state, open, {[], Code}, 30000};  

30000表示超时时间为30000毫秒,在30000毫秒之后,发生超时,超时后调用StateName(timeout, StateData) 
在这里,门打开30秒之后再次锁上: 
Java代码  收藏代码
  1. open(timeout, State) ->  
  2.   do_lock(),  
  3.   {next_state, locked, State}.  


6,所有的状态事件 
有时候一个事件可以到达gen_fsm进程的任何状态,这时消息可以使用gen_fsm:send_all_state_event/2来发送消息,然后用Module:handle_event/3来处理 
Java代码  收藏代码
  1. -module(code_lock).  
  2. ...  
  3. -export([stop/0]).  
  4. ...  
  5.   
  6. stop() ->  
  7.   gen_fsm:send_all_state_event(code_lock, stop).  
  8. ...  
  9.   
  10. handle_event(stop, _StateName, StateData) ->  
  11.   {stop, normal, StateData}.  


7,停止 
7.1 在Supervision Tree里 
如果gen_fsm在supervision tree里,则不需要stop方法,gen_fsm会自动被supervisor停止 
如果需要在结束前清理数据,那么shutdown strategy必须为一个timeout,并且必须在gen_fsm的init方法里设置捕获exit信号,然后 
gen_fsm进程会调用callback方法terminate(shutdown, StateName, StateData) 
Java代码  收藏代码
  1. init(Args) ->  
  2.   ...,  
  3.   process_flag(trap_exit, true),  
  4.   ...,  
  5.   {ok, Statename, StateData}.  
  6. ...  
  7. terminate(shutdown, StateName, StateData} ->  
  8.   ..code for cleaning up here..  
  9.   ok.  

7.2 单独的Gen_Fsms进程 
如果gen_fsm进程没有supervisor,则需要一个stop方法: 
Java代码  收藏代码
  1. ...  
  2. -export([stop/0]).  
  3. ...  
  4.   
  5. stop() ->  
  6.   gen_fsm:send_all_state_event(code_lock, stop).  
  7. ...  
  8.   
  9. handle_event(stop, _StateName, StateData) ->  
  10.   {stop, normal, StateData}.  
  11. ...  
  12.   
  13. terminate(normal, _StateName, _StateData) ->  
  14.   ok.  

callback方法处理stop事件并返回{stop, normal, StateData1},normal表示正常停止,StateData1为gen_fsm的新的data data值 
这将导致gen_fsm调用terminate(normal, StateName, StateData1)然后优雅的停止 

8,处理其他消息 
其他消息的处理callback方法为handl_info(Info, StateName, StateData),例如连接到其他非supervisor进程并捕获exit信号时处理exit消息: 
Java代码  收藏代码
  1. handle_info({'EXIT', Pid, Reason}, StateName, StateData) ->  
  2.   ..code to handle exits here..  
  3.   {next_state, StateName1, StateData1}.  


补充:gen_fsm exports and callbacks 
Java代码  收藏代码
  1. gen_fsm module                          Callback module  
  2. gen_fsm:start_link -------------------> Module:init/1  
  3. gen_fsm:start  
  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. gen_fsm:reply  
  9. gen_fsm:send_event_after  
  10. gen_fsm:start_timer  
  11. gen_fsm:cancel_timer  
  12. gen_fsm:enter_loop  
  13.                                         Module:handle_info/3  
  14.                                         Module:terminate/3  
  15.                                         Module:code_change/4  

 

 

posted @ 2011-04-07 15:23  麦飞  阅读(1517)  评论(0编辑  收藏  举报