erlang四大behaviour之二-gen_fsm


start_link跟gen-sever 类似,启动后进入init初始化,结果是 ok,StateName,State. 此例子中是初始化密码门的原始密码,置输入密码为空

1. 有限状态机

有限状态机可以用下面这个公式来表达

State(S) x Event(E) -> Actions(A), State(S')

表示的就是在S状态时如果有事件E发生,那么执行动作A后把状态调整到S’。理解很好理解,如果能够熟练应用必须得下苦功,多练习。


start_link跟gen-sever 类似,启动后进入init初始化,结果是 ok,StateName,State. 此例子中是初始化密码门的原始密码,置输入密码为空

2. 一个例子

erlang手册中用这个例子来解释的:开锁问题,有一个密码锁的门,它就可以看作一个状态机,初始状态门是锁着的,任何时候有人按一个密码键就会产生一个事件,这个键值和前面的按键组合后与密码相比较,看是否正确,如果输入的密码顺序是对的,那么将门打开10秒,如果输入密码不完全,则等待下次按钮按下,如果输入密码顺序是错的,则重新开始等待按键按下。


修改了下,加入了状态声明,感觉更容易懂。。。

代码:


-module(test_fsm).

-behaviour(gen_fsm).

-export([start_link/1]).

-export([input_pwd/1,locked/2,open/2,stop/0,close_door/0]).

%callback ; 此类函数必须存在,且导出
-export([init/1,code_change/4,handle_event/3,handle_info/3,handle_sync_event/4,terminate/3]).

-record(state,{
                inputed_pwd,    %输入的密码
                pwd             %门禁原始密码
                }).

start_link(Code) ->
    %                   (Name,CallBackModule,InitArgs,Opts)
    gen_fsm:start_link({local, ?MODULE}, ?MODULE, Code, []).
    
input_pwd(InputWord) ->
    %                   (Name,Events)
    gen_fsm:send_event(?MODULE, {input_pwd, InputWord}).
    
close_door() ->
    %                   (Name,Events)
    gen_fsm:send_event(?MODULE, close).
    
init(Code) ->
    %{ok,StateName, State}
    {ok, locked, #state{inputed_pwd=[],pwd=Code}}.
    
    
locked(close, State) ->
     io:format("door's already closed ~n",[]),  
    {next_state, locked, State};
    
% stateName(Event,State)   
locked({input_pwd, InputWord}, State) ->
    #state{inputed_pwd=InputedPwd,pwd=PWD} = State,
    io:format("locked act; input is ~p;InputPre:~w;PWD:~w; ~n;",[InputWord,InputedPwd,length(PWD)]), 
    case [InputWord|InputedPwd] of
        PWD ->%  input pwd equal door's pwd;correct
            TimeOut = 10000,
            do_unlock(TimeOut),
            %{nextstate,StateName,State,TimeOutMiniSeconds}
            {next_state, open, State#state{inputed_pwd=[]}, TimeOut};
        InputedPwd1 when length(InputedPwd1)<length(PWD) ->
            io:format("input;current input is ~p ~n",[InputedPwd1]),  
            %{next_state,StateName,State}
            {next_state, locked, State#state{inputed_pwd=InputedPwd1}};
        _Wrong ->
             io:format("input;current input is WRONG :~w ;~p;~p;",[_Wrong,length(_Wrong),length(PWD)]),  
            {next_state, locked, State#state{inputed_pwd=[]}}
    end.
    
open(close, State) ->
    io:format("action close ~n",[]),  
    do_lock(),     
    {next_state, locked, State};
    
open(timeout, State) ->
    do_lock(),
    {next_state, locked, State}.
 
code_change(_OldVsn, StateName, Data, _Extra) ->  
    {ok, StateName, Data}.  

    
handle_event(stop, _StateName, StateData) ->
    io:format("handle_event stop ~n"),  
    {stop, normal, StateData};
    
handle_event(Event, StateName, Data) ->  
    io:format("handle_event... ~n"),  
    unexpected(Event, StateName),  
    {next_state, StateName, Data}.  
  
handle_sync_event(Event, From, StateName, Data) ->  
    io:format("handle_sync_event, for process: ~p... ~n", [From]),  
    unexpected(Event, StateName),  
    {next_state, StateName, Data}.  
  
handle_info(Info, StateName, Data) ->  
    io:format("handle_info...~n"),  
    unexpected(Info, StateName),  
    {next_state, StateName, Data}.  
    
stop() ->
    gen_fsm:send_all_state_event(?MODULE, stop).
    

    
terminate(normal, _StateName, _StateData) ->
    ok.  
  
%% Unexpected allows to log unexpected messages  
unexpected(Msg, State) ->  
    io:format("~p RECEIVED UNKNOWN EVENT: ~p, while FSM process in state: ~p~n",  
              [self(), Msg, State]).  
%%  
%% actions  
do_unlock(TimeOut) ->  
    io:format("open the DOOR. Door will be closed in ~p miniseconds ~n",[TimeOut]).  
  
do_lock() ->  
    io:format("close the DOOR.~n").  
    
    

运行:

80> test_fsm:start_link([1,2,3]).      
{ok,<0.265.0>}
81> test_fsm:input_pwd(3).       
locked act; input is 3;InputPre:[];PWD:3; 
;input;current input is [3] 
ok
82> test_fsm:input_pwd(2).
locked act; input is 2;InputPre:[3];PWD:3; 
;input;current input is [2,3] 
ok
83> test_fsm:input_pwd(1).
locked act; input is 1;InputPre:[2,3];PWD:3; 
;open the DOOR. Door will be closed in 10000 miniseconds 
ok
84> test_fsm:close_door().
close the DOOR.
action close 
ok

88> test_fsm:close_door().       
door's already closed 
ok



gen_fsm_send_event(Name,Events)  发送事件


stateName(Events,State)  当前的状态函数响应事件,也可以不处理响应,也可以在多个状态响应该时间,结果是{next_state,StateName,State} / {next_state,StateName,State ,TimeOut} /stop... 详细看官方文档。


input_pwd  可以输入密码,当密码长度小于原始密码,则累积,等于则比较,相等则打开门,错误则清空。

open设置超时时间,如超时,发送timeout 事件。

close_door 可以关门,在关上和打开的状态分别不同的响应。


这些代码下面解释

 

3. 启动状态机



start_link跟gen-sever 类似,启动后进入init初始化,结果是 ok,StateName,State. 此例子中是初始化密码门的原始密码,置输入密码为空

 

start_link调用gen_fsm:start_link/4,启动一个新的gen_fsm进程并连接。
1)第一个参数{local, code_lock}指定名字,在本地注册为code_lock
2)第二个参数code_lock是回调模块
3)第三个参数Code是传递给回调模块init函数的参数,就是密码锁的密码
4)第四个[]是状态机的选项
如果进程注册成功,则新的gen_fsm进程调用code_lock:init(Code),返回{ok, StateName, StateData}。StateName是gen_fsm的初始状态,在这里返回的是locked,表示初始状态下门是锁着的,StateData是gen_fsm的内部状态,在这里Statedata是当前的按键顺序(初始时为空)和正确的锁代码,是个列表

init(Code) ->
    {ok, StateName, State}.

注意gen_fsm:start_link是同步的,直到gen_fsm进程初始化并准备好开始接受请求时才会返回。加入gen_fsm是监控树的一部分,那么gen_fsm:start_link必须被使用,也就是被一个监控者调用,gen_fsm:start则是启动单独的gen_fsm进程,也就是gen_fsm不是监控树的一部分

4. 事件通知

使用gen_fsm:send_event/2来实现按建事件的通知

input_pwd(Digit) ->
    gen_fsm:send_event(Name, Event).

code_lock是gen_fsm的名字,且必须用这个名字启动进程,{button, Digit}是发送的事件,事件是作为消息发送给gen_fsm的,当事件被接收到,gen_fsm就调用StateName(Event, StateData),它的返回值应该是{next_state, StateName1, StateData1}。StateName是当前状态名称,而StateName1是将转换到的下一状态名称,StateData1是StateData的新值

假如门是锁着的且按了一个按键,完整的按键序列和密码相比较,根据比较结果来决定门是打开(状态切到open)还是保持locked状态。

5 超时

假如输入的密码正确,门被打开,locked/2函数返回下面的序列

{next_state, stateName, State, 30000};

10000毫秒,在10秒后,超时发生,调用StateName(timeout, StateData) ,门又重新锁上

 

6. 所有状态事件

有时候一个事件可以到达gen_fsm进程的任何状态,取代用gen_fsm:send_event/2发送消息和写一段每个状态函数处理事件的代码,这个消息我们可以用gen_fsm:send_all_state_event/2 发送,用Module:handle_event/3处理

-module(code_lock).
-export([stop/0]).
stop() ->
    gen_fsm:send_all_state_event(code_lock, stop).
handle_event(stop, _StateName, StateData) ->
    {stop, normal, StateData}.

 

7. 停止

假如gen_fsm是监控树的一部分,则不需要停止方法,gen_fsm会自动被监控者停止。如果需要在结束前清理数据,那么shutdown strategy必须为一个timeout,并且必须在gen_fsm的init方法里设置捕获exit信号,然后
gen_fsm进程会调用callback方法terminate(shutdown, StateName, StateData)

init(Args) ->
    ...,
    process_flag(trap_exit, true),
    ...,
    {ok, StateName, StateData}.
terminate(shutdown, StateName, StateData) ->
    ..code for cleaning up here..
    ok.

 

8. 独立gen_fsm进程

加入gen_fsm不是监控树的一部分,stop函数可能有用,如:

-export([stop/0]).
stop() ->
    gen_fsm:send_all_state_event(code_lock, stop).
handle_event(stop, _StateName, StateData) ->
    {stop, normal, StateData}.
terminate(normal, _StateName, _StateData) ->
    ok.

回调函数处理stop事件并返回{stop, normal, StateData1},normal表示正常停止,StateData1为gen_fsm的新的StateData值,这将导致gen_fsm调用terminate(normal, StateName, StateData1)然后自然的停止

9. 处理其他信息

收到的其他消息由handle_info(Info, StateName, StateData)处理,其他消息的一个例子就是exit消息,假如gen_fsm进程与其他进程link了并且trace了信号,就要处理exit消息


handle_info({'EXIT', Pid, Reason}, StateName, StateData) ->
    ..code to handle exits here..
    {next_state, StateName1, StateData1}.


补充(2012/08/30):

fsm就是Finite State Machines的缩写.知道全称后就明白这个模块到底是干什么的了,原来是状态机,而且是叫有限状态机.

CALLBACK函数
gen_fsm需要导出一些callback函数,每个callback被触发的时机如下:

gen_fsm module

Callback module

--------------

---------------

gen_fsm:start_link

-----> Module:init/1

gen_fsm:send_event

-----> Module:StateName/2

gen_fsm:send_all_state_event

-----> Module:handle_event/3

gen_fsm:sync_send_event

-----> Module:StateName/3

gen_fsm:sync_send_all_state_event

-----> Module:handle_sync_event/4

--

-----> Module:handle_info/3

-

-----> Module:terminate/3

--

-----> Module:code_change/4


如果callback函数执行异常,或者返回了无效的值,gen_fsm进程将被终止。
具体每个callback返回值类型,参考STDLIB Reference Manual相关章节: http://www.erlang.org/doc/man/gen_fsm.html#Module:init-1


根据 http://blog.csdn.net/xyj0663/article/details/8510488 修改

start_link跟gen-sever 类似,启动后进入init初始化,结果是 ok,StateName,State. 此例子中是初始化密码门的原始密码,置输入密码为空
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值