3.Gen_Fsm行为

本章应该结合 gen_fsm(3) 来阅读,其中面有所有接口函数和回调函数的详细说明。

有限状态机

一个有限状态机FSM,可以用一个关系式来描述:

State(S) x Event(E) -> Actions(A), State(S’)

这些关系解释如下:

如果我们处在状态 S 并且事件 E 发生了,那么,我们需要执行动作 A ,并且转变到状态 S‘ 。

对于一个用 gen_fsm 行为实现的FSM来说,状态转换规则被写为符合如下约定的一系列Erlang函数:

StateName( Event, StateData ) ->
    .. 这里放动作的代码 ...
    { next_state, StateName‘, StateData‘ }

例子

一个带有密码锁的门可以被看作一个FSM。初始状态下,门是锁着的。一个人无论何时按下一个按钮都会产生一个事件。取决于之前哪些按钮被按下,当前序列可能是正确的、不完整、或者错误的。

如果是正确的,那么门会打开30秒(30000ms)。如果是不完整的,那么等待按下另一个按钮。如果是错误的,那么我们重来,等待新的按钮序列。

使用 gen_fsm 实现的密码锁可以得到以下回调模块:

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

该代码将会在下一节中进行解释。

启动一个Gen_Fsm

在上一节的例子中,该gen_fsm是通过调用 code_lock:start_link(Code) 来启动的:

start_link(Code) ->
    gen_fsm:start_link({local, code_lock}, code_lock, Code, []).

start_link 调用函数 gen_fsm:start_link/4。该函数产生并连接到一个新的gen_fsm进程.

  • 第一个参数 {local, code_lock} 指明了名字。在这里,gen_fsm会在本地注册为 code_lock 。 如果名字被省略了,那么将不会注册gen_fsm。这样就必须使用它的pid。名字也可以这样写 {global, Name}, 这样,gen_fsm就是使用global_register_name/2 来注册了。
  • 第二个参数 code_lock 是回调模块的名字,也就是回调函数所位于的模块。 在这个例子里面,接口函数(start_link 和 button)是位于和回调函数(initlocked 和 open)相同的模块中。这一般是较好的编程实践,让一个进程对应的代码包含在一个模块里面。
  • 第三个参数 Code 是一个值,将被原封不动传递给回调函数 init 。这里, init 得到锁的正确代码做为内部数据。
  • 第四个参数 [] 是一个选项的列表。参见 gen_fsm(3) 来找到可用的选项。

如果名称注册成功,新的gen_fsm进程会调用回调函数 code_lock:init(Code)。这个函数要返回 {ok, StateName, StateData},其中 StateName 是gen_fsm初始状态的名字。在这个例子里面是 locked,假设门一开始是锁着的。 StateData 是gen_fsm的内部状态。(对于gen_fsm来说,内部状态一般是指“状态数据”(state data),以区别于状态机的状态)。在这个例子里面,状态数据(state data)是到目前为止的按钮的顺序(开始的时候为空)和锁的正确密码顺序。

init( Code ) ->
    {ok, locked, {[], Code}}.

注意gen_fsm:start_link是同步的。它只有到gen_fsm已经完成初始化并且可以接收通知的时候才返回。

如果gen_fsm是一个监毒树的一部分的话——即由督程启动的——必须使用 gen_fsm:start_link 。有另外一个函数 gen_fsm:start 来启动一个独立的gen_fsm——即,gen_fsm不属于监督树的一部分。

事件通知

通知密码锁一个按钮被按下的事件是使用 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的状态数据的新的值。

locked({button, Digit}, {SoFar, Code}) ->
    case [Digit|SoFar] of
        Code ->
            do_unlock(),
            {next_state, open, {[], Code}, 30000};
        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}.

如果门是锁着的,且按下了按钮,那么,到目前位置所有的按钮序列就会与密码锁正确的密码进行比较,然后根据比较的结果,要么门是解锁了gen_fsm进入打开的状态 open ,或者是仍然是处于锁着的状态 locked 。

超时

当一个正确的按钮顺序被按下之后,门会被打开,下面的元组就会从函数 locked/2 返回:

{next_state, open, {[], Code}, 3000};

30000 是一个以毫秒为单位的超时值。30000ms,也就是30秒后,就会发生一个超时。然后 StateName(timeout,StateData) 就会被调用。在这个例子里面,当门处于状态 open (打开)30秒后就会发生超时。然后门又会被锁上:

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

所有的状态事件

有时候,在gen_fsm的任何状态都有可能有事件到达。除了可以用 gen_fsm:send_event/2 发送消息,并为每一个状态函数写一个子句来处理事件之外,还可以通过 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}.

停止

在监督树中

如果gen_fsm是监督树的一部分,那么不需要停止函数。它的督程会自动停止它。具体如何进行是通过督程中的关闭策略集来定义。

如果需要在终止前先进行清除操作,那么关闭策略必须是一个超时值,并且,gen_fsm必须在 init 函数里面设置成捕捉退出信号。当被要求关闭时,gen_fsm会调用回调函数 terminate(shutdown, StateName, StateData):

init(Args) ->
    ...,
    process_flag(trap_exit, true),
    ...,
    {ok, StateName, StateData}.

...

terminate(shutdown, StateName, StateData) ->
    ..code for cleaning up here..
    ok.

独立的Gen_Fsm

如果gen_fsm不是监督树的一部分,那么一个停止函数是有用的,例如:

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

处理 stop 事件的回调函数返回一个元组 {stop,normal,StateData1},这里的 normal 指明了这是一个正常的终止,同时 StateData1 是gen_fsm状态数据的新值。这会使gen_fsm去调用terminate(normal, StateName, StateData1 ),然后优雅的终止。

处理其他消息

如果gen_fsm需要接收事件之外的消息,那么必须实现回调函数 handle_info(Info, StateName, StateData) 来处理这些消息。其他的消息比如退出消息,如果gen_fsm是联接到其他的进程(非督程)的,并且需要捕获退出信号。

handle_info({’EXIT’, Pid, Reason}, StateName, StateData) ->
    ..code to handle exits here..
    {next_state, StateName1, StateData1}.
时间: 2024-10-29 19:13:29

3.Gen_Fsm行为的相关文章

Erlang OTP学习(1)gen_fsm

1. 有限状态机 有限状态机可以用下面这个公式来表达 State(S) x Event(E) -> Actions(A), State(S') 表示的就是在S状态时如果有事件E发生,那么执行动作A后把状态调整到S’.理解很好理解,如果能够熟练应用必须得下苦功,多练习. start_link跟gen-sever 类似,启动后进入init初始化,结果是 ok,StateName,State. 此例子中是初始化密码门的原始密码,置输入密码为空 2. 一个例子 erlang手册中用这个例子来解释的:开锁

erlang四大behaviour之二-gen_fsm

来源:http://www.cnblogs.com/puputu/articles/1701012.html 今天介绍erlang的一个非常重要的behaviour,就是gen_fsm-有限状态机,有限状态机的作用非常之多,比如文本解析,模式匹配.游戏逻辑等等方面的处理都是它的强项,所以这个behaviour非常之重要 1. 有限状态机 有限状态机可以用下面这个公式来表达 State(S) x Event(E) -> Actions(A), State(S') 表示的就是在S状态时如果有事件E发

gen_fsm的学习笔记

网上搜索gen_fsm的例子,90%都是code_lock,依葫芦画瓢弄了下,记录一些学习心得 init(UnLockCode) -> process_flag(trap_exit,true), io:format("init: ~p~n", [UnLockCode]), {ok, locked, {[], UnLockCode}}. init返回值里面,locked为StateName,{[], UnLockCode}为StateData,在回调函数里面有用 发送event有两

Erlang OTP设计原则Gen_Fsm行为[转]

转自: http://www.cnblogs.com/yourihua/archive/2012/05/13/2497776.html 1. Fsm 称为 有限状态机,举个例子,游戏中的怪物称为NPC,NPC一般有几种状态,比如:静止,移动,死亡,被攻击,攻击英雄等等几个有限的状态,那么我们就可以有限状态机实现NPC的状态变更. 一个有限状态机可以用一个关系式来描述,State(静止状态S1) x Event(英雄进入视野范围事件E) -> Actions(开始移动动作A), State(移动状

Erlang学习: EUnit Testing for gen_fsm

背景:gen_fsm 是Erlang的有限状态机behavior,非常有用.爱立信的一位TDD大神写了一篇如何测试gen_fsm,这个fsm是一个交易系统,负责简单的交易员登陆,插入item,删除item等等,翻译如下: 1. Start and Stop 先看下最初版本的tradepost_tests: -module(tradepost_tests). -include_lib("eunit/include/eunit.hrl"). % This is the main point

Gen_fsm行为实践与分析

1.简介 Gen_fsm是一个通用的有限状态机,它描述了这样的一组关系: State(S) x Event(E) -> Actions(A),State(S') 这个关系意味着:如果在S状态下发生事件E,将执行动作A并返回状态S'.对于一个FSM实现可以使用gen_fsm行为来实现,它提供了标准的接口函数和回调函数.并且,gen_fsm进程可以安装在supervisor监控树中.回调函数与导出函数的关系如下: 1 gen_fsm moduleCallbackmodule 2 ----------

Riak Core Guide

Learn Riak Core Step By Step riak core 是 riak的主要组成部分,主要负责分布式的部分,虽然官方有自己的存储后端,但是我们也可以使用其他的后端存储. Partitioning & Distributing Work riak core 在每个节点上都是使用master/worker配置,这样作为一个工作单元来执行,riak core的worker为vnode worker, 在每个节点上由riak_core_sup生成,vnode worker对应的模块为

【转】erlang四种监控策略one_for_one、one_for_all、simple_one_for_one、rest_for_one

Supervisor Behaviour是一个用来实现一个supervisor进程来监控其他子进程的模块 子进程可以是另一个supervisor,也可以是一个worker进程 worker进程一般使用gen_event,gen_fsm或gen_server behaviour来实现 一个使用该模块来实现的supervisor有一个接口方法的标准集,包括跟踪和错误报告的功能 supervisor用来构建一个分层进程结构,称为supervision tree,这是组织一个容错系统的好方式 1,Sup

Erlang 104 OTP - incomplete

笔记系列 Erlang环境和顺序编程Erlang并发编程Erlang分布式编程YawsErlang/OTP 日期              变更说明 2014-12-21 A Outline, 1 Agenda 0 Scope 围绕OTP设计原则,分别记录行为模式.监督树概念.应用.发布和部署,以及[3]中一个产品级缓存解决方案. 1 OTP Design Principles Erlang Doc: OTP Design Principles User's Guide OTP设计原则阐述的是如