当前位置:首页 > 开发 > 开源软件 > 正文

gen_fsm的例子

发表于: 2012-03-12   作者:cryolite   来源:转载   浏览:
摘要: 《Erlang OTP in action》一书完全略过了对gen_fsm的介绍,因为作者认为这是一个很少会用的的behaviour。但是最近看riak_core源代码的时候,发现它的vnode实现是基于gen_fsm的。Erlang/OTP官方文档(在 这里)介绍gen_fsm有限状态机的例子代码不全,而且代码的逻辑似乎有问题。当然可能官方认为例子太简单了,我们会自动补全。不过如果每过一段时间
《Erlang OTP in action》一书完全略过了对gen_fsm的介绍,因为作者认为这是一个很少会用的的behaviour。但是最近看riak_core源代码的时候,发现它的vnode实现是基于gen_fsm的。Erlang/OTP官方文档(在 这里)介绍gen_fsm有限状态机的例子代码不全,而且代码的逻辑似乎有问题。当然可能官方认为例子太简单了,我们会自动补全。不过如果每过一段时间重看代码总得再补全总是件麻烦事,在此记下备忘。

简单的说官方文档中提供的是一个开密码锁(code_lock)的有限状态机例子。
  • 用户在初始启动有限状态机时会设置锁的出厂密码,然后进入“锁定(locked)”状态等待用户按键输入密码;
  • 用户通过调用code_lock:button/1输入密码,在用户输入的过程中会记录当前为止录入的健值。如果密码错误或者录入不完整,保持锁定状态。
  • 如果密码正确,那么就进入“解锁(open)”状态,并执行相关操作(do_unlock),比如打开大门。
  • 当解锁状态持续一段时间后,自动进入锁定状态,并执行相关操作(do_lock),如关门。

密码数据和用户按键输入的数据都用字符串表示。

gen_fsm的状态是由函数表示的,我开始时感觉蛮诡异的,把它理解成当前FSM进程执行到这个状态函数就好了。
从一个状态跳到下一个状态是通过状态函数的返回值控制的,返回值统一这样:
{next_state,NextStateName,NewStateData}
{next_state,NextStateName,NewStateData,Timeout}
{next_state,NextStateName,NewStateData,hibernate}
{stop,Reason,NewStateData}
NextStateName就是下一个状态函数的名字了。

文档中有两个地方提到timeout,一个是gen_fsm:start_link时最后一个控制选项中的timeout,这是控制init/1执行的超时的,不是为FSM进程运行时的状态设置超时。start_link执行时会调用init/1回调函数,直到后者执行完成FSM的启动才算完成,这里的timout控制init/1回调函数的执行不要太久。

似乎不能给FSM进程设置一个缺省的超时,我们必须在每次状态切换(状态函数的返回值,{next_state, _, _, Timeout})时为下一个状态设置超时时间。第一次进入初始状态时的超时设置是在init/1的返回值中设置。(实际上gen_server的超时设置也是这样的)

为了说明超时的使用,我在例子中加入了密码输入时间的控制,如果5秒钟内用户没有输入下一个键,自动清空历史记录,用户必须重新输入。另外还设置了开门时间超过10秒钟,自动关门进入锁定状态。

注意如果FSM在当前状态收到的事件是无法处理的,则整个状态机进程会被迫退出。试试
gen_fsm:send_event(code_lock, foooo).

关于handle_event回调函数:用来处理gen_fsm:send_all_state_event 发送给FSM的事件。无论FSM进程当前处于何种状态,当gen_fsm:send_all_state_event被调用时,状态机会调用handle_event回调函数处理。

关于handle_info回调函数:与gen_server类似,处理所有直接发给FSM进程的消息。例子:
13> code_lock:start_link("abc123").              
init: "abc123"
{ok,<0.52.0>}
14> pid(0,52,0) ! hello.
handle_info...
hello
<0.52.0> RECEIVED UNKNOWN EVENT: hello, while FSM process in state: locked

例子的演示,
~/workspace/fsm_test$ erl
Erlang R14B04 (erts-5.8.5) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8.5  (abort with ^G)
1> c(code_lock).
{ok,code_lock}

锁的密码设置为abc123
2> code_lock:start_link("abc123").
init: "abc123"
{ok,<0.39.0>}

用户输入密码
3> code_lock:button("ab").
buttion: "ab", So far: [], Code: "abc123"
ok
4> code_lock:button("c").
buttion: "c", So far: "ab", Code: "abc123"
ok

输入完成,密码正确,开门
5> code_lock:button("123").
buttion: "123", So far: "abc", Code: "abc123"
ok
open the DOOR.

发送fooo事件给FSM处理。
6> gen_fsm:send_all_state_event(code_lock, fooo).
handle_event...
ok
<0.39.0> RECEIVED UNKNOWN EVENT: fooo, while FSM process in state: open
7> gen_fsm:send_all_state_event(code_lock, fooo).
handle_event...
ok
<0.39.0> RECEIVED UNKNOWN EVENT: fooo, while FSM process in state: open
8> gen_fsm:send_event(code_lock, foooo).

=ERROR REPORT==== 12-Mar-2012::19:06:46 ===
** State machine code_lock terminating

9> erlang:is_process_alive(pid(0,39,0)).
false

上代码:
-module(code_lock).
-behaviour(gen_fsm).

-export([start_link/1]).
-export([button/1]).

-export([init/1, locked/2, open/2]).
-export([code_change/4, handle_event/3, handle_info/3, handle_sync_event/4, terminate/3]).

-spec(start_link(Code::string()) -> {ok,pid()} | ignore | {error,term()}).
start_link(Code) ->
    gen_fsm:start_link({local, code_lock}, code_lock, Code, []).

-spec(button(Digit::string()) -> ok).
button(Digit) ->
    gen_fsm:send_event(code_lock, {button, Digit}).

init(LockCode) ->
    io:format("init: ~p~n", [LockCode]),
    {ok, locked, {[], LockCode}}.

locked({button, Digit}, {SoFar, Code}) ->
    io:format("buttion: ~p, So far: ~p, Code: ~p~n", [Digit, SoFar, Code]),
    InputDigits = lists:append(SoFar, Digit),
    case InputDigits of
        Code ->
            do_unlock(),
            {next_state, open, {[], Code}, 10000};
        Incomplete when length(Incomplete)<length(Code) ->
            {next_state, locked, {Incomplete, Code}, 5000};
        Wrong ->
            io:format("wrong passwd: ~p~n", [Wrong]),
            {next_state, locked, {[], Code}}
    end;
locked(timeout, {_SoFar, Code}) ->
    io:format("timout when waiting button inputting, clean the input, button again plz~n"),
    {next_state, locked, {[], Code}}.

open(timeout, State) ->
    do_lock(),
    {next_state, locked, State}.

code_change(_OldVsn, StateName, Data, _Extra) ->
    {ok, StateName, Data}.

terminate(normal, _StateName, _Data) ->
    ok.

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}.


%% 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() ->
    io:format("passwd is right, open the DOOR.~n").

do_lock() ->
    io:format("over, close the DOOR.~n").



如果觉得这个例子太简单,可以试试 这里的用FSM做在线交易的例子。用了两个状态机代表甲乙两方做交易。

gen_fsm的例子

  • 0

    开心

    开心

  • 0

    板砖

    板砖

  • 0

    感动

    感动

  • 0

    有用

    有用

  • 0

    疑问

    疑问

  • 0

    难过

    难过

  • 0

    无聊

    无聊

  • 0

    震惊

    震惊

版权所有 IT知识库 CopyRight © 2009-2015 IT知识库 IT610.com , All Rights Reserved. 京ICP备09083238号