Gen_Fsm Behaviour
1,有限状态机
FSM,有限状态机,可以用以下形式来描述做一个关系集:
意思是,如果我们位于状态S,然后事件E发送了,则我们应该执行动作A,并且将状态改为S'
2,例子
有一个代码锁的门,如果输入的代码顺序是对的,那么将门打开30秒
如果输入代码不完全,则等待下次按钮按下,如果输入代码顺序是错的,则重新开始等待按钮按下
3,启动Gen_Fsm
上面的例子里,使用code_lock:start_link(Code)启动gen_fsm:
FSM,有限状态机,可以用以下形式来描述做一个关系集:
Java代码
- State(S) x Event(E) -> Actions(A), State(S')
意思是,如果我们位于状态S,然后事件E发送了,则我们应该执行动作A,并且将状态改为S'
2,例子
有一个代码锁的门,如果输入的代码顺序是对的,那么将门打开30秒
如果输入代码不完全,则等待下次按钮按下,如果输入代码顺序是错的,则重新开始等待按钮按下
Java代码
- -module(code_lock).
- -behaviour(gen_fsm).
- -export([start_link/1]).
- -export([button/1]).
- -export([init/1, locked/2, open/2]).
- start_link(Code) ->
- gen_fsm:start_link({local, code_lock}, code_lock, Code, []).
- button(Digit) ->
- gen_fsm:send_event(code_lock, {button, Digit}).
- init(Code) ->
- {ok, locked, {[], Code}}.
- locked({button, Digit}, {SoFar, Code}) ->
- case [Digit|SoFar] of
- Code ->
- do_unlock(),
- {next_state, open, {[], Code}, 3000};
- Incomplete when length(Incomplete) < length(Code) ->
- {next_state, locked, {Incomplete, Code}};
- _Wrong ->
- {next)state, locked, {[], Code}};
- end.
- open(timeout, State) ->
- do_lock(),
- {next_state, locked, State}.
3,启动Gen_Fsm
上面的例子里,使用code_lock:start_link(Code)启动gen_fsm:
Java代码
- start_link(Code) ->
- 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代码
- init(Code) ->
- {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代码
- button(Digit) ->
- 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代码
- locked({button, Digit}, {SoFar, Code}) ->
- case [Digit|SoFar] of
- Code ->
- do_unlock(),
- {next_state, open, {[], Code}, 3000};
- Incomplete when length(Incomplete) < length(Code) ->
- {next_state, locked, {Incomplete, Code}};
- _Wrong ->
- {next_state, locked, {[], Code}};
- end.
- open(timeout, State) ->
- do_lock(),
- {next_state, locked, State}.
如果门是锁着的,这时按下一个按钮,则比较目前按钮的顺序和正确的代码顺序,由比较结果来决定是unlock门还是继续保持locked
5,超时
当输入正确的按钮顺序后,门unlock然后返回如下的tuple:
30000表示超时时间为30000毫秒,在30000毫秒之后,发生超时,超时后调用StateName(timeout, StateData)
在这里,门打开30秒之后再次锁上:
6,所有的状态事件
有时候一个事件可以到达gen_fsm进程的任何状态,这时消息可以使用gen_fsm:send_all_state_event/2来发送消息,然后用Module:handle_event/3来处理
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)
7.2 单独的Gen_Fsms进程
如果gen_fsm进程没有supervisor,则需要一个stop方法:
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消息:
补充:gen_fsm exports and callbacks
当输入正确的按钮顺序后,门unlock然后返回如下的tuple:
Java代码
- {next_state, open, {[], Code}, 30000};
30000表示超时时间为30000毫秒,在30000毫秒之后,发生超时,超时后调用StateName(timeout, StateData)
在这里,门打开30秒之后再次锁上:
Java代码
- open(timeout, State) ->
- do_lock(),
- {next_state, locked, State}.
6,所有的状态事件
有时候一个事件可以到达gen_fsm进程的任何状态,这时消息可以使用gen_fsm:send_all_state_event/2来发送消息,然后用Module:handle_event/3来处理
Java代码
- -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,停止
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代码
- init(Args) ->
- ...,
- process_flag(trap_exit, true),
- ...,
- {ok, Statename, StateData}.
- ...
- terminate(shutdown, StateName, StateData} ->
- ..code for cleaning up here..
- ok.
7.2 单独的Gen_Fsms进程
如果gen_fsm进程没有supervisor,则需要一个stop方法:
Java代码
- ...
- -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.
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代码
- handle_info({'EXIT', Pid, Reason}, StateName, StateData) ->
- ..code to handle exits here..
- {next_state, StateName1, StateData1}.
补充:gen_fsm exports and callbacks
Java代码
- gen_fsm module Callback module
- gen_fsm:start_link -------------------> Module:init/1
- gen_fsm:start
- 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
- gen_fsm:reply
- gen_fsm:send_event_after
- gen_fsm:start_timer
- gen_fsm:cancel_timer
- gen_fsm:enter_loop
- Module:handle_info/3
- Module:terminate/3
- Module:code_change/4
莫愁前路无知己,天下无人不识君。