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做交易的例子。用了两个状态机代表甲乙两方做交易。

http  learnyousomeerlang.com/finite-state-machines

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值