OTP Design Principles: Gen_Fsm Behaviour

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

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

2,例子
有一个代码锁的门,如果输入的代码顺序是对的,那么将门打开30秒
如果输入代码不完全,则等待下次按钮按下,如果输入代码顺序是错的,则重新开始等待按钮按下
-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:
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是当前的按钮顺序(初始时为空)和正确的锁代码
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来实现按钮事件的通知:
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值:
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:
{next_state, open, {[], Code}, 30000};

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


6,所有的状态事件
有时候一个事件可以到达gen_fsm进程的任何状态,这时消息可以使用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,停止
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)
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方法:
...
-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消息:
handle_info({'EXIT', Pid, Reason}, StateName, StateData) ->
  ..code to handle exits here..
  {next_state, StateName1, StateData1}.


补充:gen_fsm exports and callbacks
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

你可能感兴趣的:(UP)