`
cryolite
  • 浏览: 580522 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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做在线交易的例子。用了两个状态机代表甲乙两方做交易。
分享到:
评论

相关推荐

    erlang OTP Design Principles之Gen中文

    这个例子展示了如何使用Gen_Fsm行为来实现这样的状态机。回调模块`code_lock`包含了初始化、锁定和开启状态的处理函数,如`init/1`、`locked/2`和`open/2`。 `start_link/1`函数用于启动一个Gen_Fsm实例。在示例中...

    Erlang中文手册.pdf

    - **2.3.1 有限状态机**:Erlang中的Gen_Fsm行为允许实现有限状态机。 - **2.3.2 实例**:提供了一个Gen_Fsm行为的实例。 - **2.3.3 启动一个Gen_Fsm**:如何启动一个有限状态机。 - **2.3.4 事情通知**:事件被...

    lbm_pg:进程组的另一种方法提供类似于 pg2 的实现,而无需使用具有集成功能的全局工具来(可靠地)向具有故障转移、超时、成员缓存等功能的组成员发送消息

    实现gen_server或gen_fsm行为的每个进程都可以加入一个组。更多信息有关更多信息,请查看全面的内联 EDoc 文档。依赖关系lbm_pg使用来池化异步发送的工作进程。 别怕, worker_pool项目本身的依赖只是测试。 这意味...

    Erlang中文手册

    - Gen_Fsm: 通用有限状态机行为。 **2.1.3 应用** - **定义**: 一个或多个模块和服务的集合,共同完成特定功能。 - **示例**: - 应用可以包含一个Web服务器、数据库接口等。 **2.1.4 发布** - **定义**: 将...

    erlang_standard_snippets-源码.rar

    8. **行为(Behaviours)**: Erlang的行为如gen_server、gen_event和gen_fsm,提供了标准的服务器、事件管理和有限状态机的实现框架。源码中可能会有这些行为的实例。 9. **并发与分布式(Concurrency and ...

    erlang入门手册

    - **Gen_Server、Gen_Fsm、Gen_Event**: OTP定义了多种通用的服务器行为,它们基于状态机和事件处理原则,允许开发者专注于业务逻辑的实现。 ### 总结 Erlang入门手册深入浅出地介绍了Erlang语言的基本概念,包括...

    erlang_examples:演示ERLANG所有功能的源代码集合

    10. **行为和回调**: Erlang中的行为(如GenServer、GenEvent和Gen_fsm)定义了特定的生命周期和事件处理模式。这些行为可以通过实现预定义的回调函数来定制,以满足特定需求。 总的来说,erlang_examples项目是一...

    数字信号分频器与激励文件的verilog代码

    2. **状态机**:为了跟踪分频过程,我们可以使用一个有限状态机(FSM)。状态机通常包含几个状态,如IDLE、COUNTING和OUTPUT,用于表示分频器的不同工作阶段。 3. **计数器**:在COUNTING状态下,用一个计数器(`...

Global site tag (gtag.js) - Google Analytics