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发生,那么执行动作A后把状态调整到S’。理解很好理解,如果能够熟练应用必须得下苦功,多练习。

2. 一个例子

erlang手册中用这个例子来解释的:开锁问题,有一个密码锁的门,它就可以看作一个状态机,初始状态门是锁着的,任何时候有人按一个密码键就会产生一 个事件,这个键值和前面的按键组合后与密码相比较,看是否正确,如果输入的密码顺序是对的,那么将门打开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. 启动状态机

在上一节提到的例子里,我们使用code_lock:start_link(Code)启动gen_fsm

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

start_link调用gen_fsm:start_link/4,启动一个新的gen_fsm进程并连接。
1)第一个参数{local, code_lock}指定名字,在本地注册为code_lock
2)第二个参数code_lock是回调模块
3)第三个参数Code是传递给回调模块init函数的参数,就是密码锁的密码
4)第四个[]是状态机的选项
如果进程注册成功,则新的gen_fsm进程调用code_lock:init(Code),返回{ok, StateName,
StateData}。StateName是gen_fsm的初始状态,在这里返回的是locked,表示初始状态下门是锁着的,StateData是
gen_fsm的内部状态,在这里Statedata是当前的按键顺序(初始时为空)和正确的锁代码,是个列表

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

注意gen_fsm:start_link是同步的,直到gen_fsm进程初始化并准备好开始接受请求时才会返回。加入gen_fsm是监控树的一部 分,那么gen_fsm:start_link必须被使用,也就是被一个监控者调用,gen_fsm:start则是启动单独的gen_fsm进程,也就 是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是 StateData的新值

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

假如门是锁着的且按了一个按键,完整的按键序列和密码相比较,根据比较结果来决定门是打开(状态切到open)还是保持locked状态。

5 超时

假如输入的密码正确,门被打开,locked/2函数返回下面的序列

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

30000表示超时30000毫秒,在30秒后,超时发生,调用StateName(timeout, StateData) ,门又重新锁上

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

6. 所有状态事件

有时候一个事件可以到达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}.

7. 停止

假如gen_fsm是监控树的一部分,则不需要停止方法,gen_fsm会自动被监控者停止。如果需要在结束前清理数据,那么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.

8. 独立gen_fsm进程

加入gen_fsm不是监控树的一部分,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.

回调函数处理stop事件并返回{stop, normal, StateData1},normal表示正常停止,StateData1为gen_fsm的新的StateData值,这将导致gen_fsm调用 terminate(normal, StateName, StateData1)然后自然的停止

9. 处理其他信息

收到的其他消息由handle_info(Info, StateName, StateData)处理,其他消息的一个例子就是exit消息,假如gen_fsm进程与其他进程link了并且trace了信号,就要处理exit消息

handle_info({‘EXIT‘, Pid, Reason}, StateName, StateData) ->
    ..code to handle exits here..
    {next_state, StateName1, StateData1}.

补充(2012/08/30):

fsm就是Finite State Machines的缩写.知道全称后就明白这个模块到底是干什么的了,原来是状态机,而且是叫有限状态机.

CALLBACK函数
gen_fsm需要导出一些callback函数,每个callback被触发的时机如下:

gen_fsm module


Callback module


--------------


---------------


gen_fsm:start_link


-----> Module:init/1


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


--


-----> Module:handle_info/3


-


-----> Module:terminate/3


--


-----> Module:code_change/4

如果callback函数执行异常,或者返回了无效的值,gen_fsm进程将被终止。
具体每个callback返回值类型,参考STDLIB Reference Manual相关章节: http://www.erlang.org/doc/man/gen_fsm.html#Module:init-1
时间: 2024-10-07 09:00:33

erlang四大behaviour之二-gen_fsm的相关文章

erlang四大behaviour之四-supervisor

http://www.cnblogs.com/puputu/articles/1689621.html 1. 监督规则 一个监督者负责启动.停止.监控他的子进程.监督者的一个基本概念就是当必要的时候重启子进程保证它们的存活 哪个子进程要重启和被监控是由一个子规程列表决定的,子进程按照列表中指定的顺序启动,并按相反的顺序终止 2. 实例 监督者的回调模块 -module(ch_sup).  -behaviour(supervisor).  -export([start_link/0]).  -ex

erlang四大behaviour之三-gen_event

来源:http://www.cnblogs.com/puputu/articles/1689623.html 1. 事件处理规则 在OTP中,事件管理器是一个事件可以发送到的命名对象,一个事件可以是一个错误.一个警告.或者一些要写入日志的信息 在事件管理器中,有0个.一个或者多个事件处理器被安装,当事件管理器被一个事件通知时,这个事件将被安装在事件管理器中的事件处理器处理, 事件管理器用一个进程实现,事件处理器用回调模块实现.事件管理器本质上维护一个{Module, State}列表,每一个Mo

4.AngularJS四大特征之二: 双向数据绑定

AngularJS四大特征之二: 双向数据绑定 (1)方向一:把Model数据绑定到View上--此后不论何时只要Model发生了改变,则View中的呈现会立即随之改变!实现方法: {{ }}.ngBind.ngRepeat.ngIf.ngSrc.ngStyle...都实现了方向1的绑定. (2)方向二:把View(表单控件)中修改绑定到Model上--此后不论任何时候,只要View中的数据一修改,Model中的数据会自动随之修改.实现方法:  只有ngModel指令. 提示:可以使用$scop

erlang与设计模式(二)——工厂、抽象工厂、建造者

Russell-X-Shanso 工厂模式.抽象工厂模式.建造者模式,均为创建类模式,其共有的设计思路主要在于根据情况理清并封装创建流程(创建进程.创建gen_server.组建record或maps等复合数据结构等).解耦.定向扩展等等. (注:由于这三个创建类模型解决的问题近似,面向对象语言中的解决方式也较为近似,因此我们放在一起来讨论:) 3.工厂模式( Factory Method ) (1)意图:定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类

Android深入四大组件(二)Service的启动过程

相关文章 Android深入理解四大组件系列 前言 此前我用较长的篇幅来介绍Android应用程序的启动过程(根Activity的启动过程),这一篇我们接着来分析Service的启动过程.建议阅读此篇文章前,请先阅读Android深入四大组件(一)应用程序启动过程(前篇)和Android深入四大组件(一)应用程序启动过程(后篇)这两篇文章. 1.ContextImpl到ActivityManageService的调用过程 要启动Service,我们会调用startService方法,它的实现在C

11、四大组件之二-Service高级(二)Native Service

一.Service的分类 1.1>Android Service 使用Java编写在JVM中运行的服务 1.2>Native Service 使用C/C++完成的服务,一般在系统开始时完成初始化,如MediaService, Audio Service等. 二.Native Service工作流程分析 int main(int argc, char** argv) { sp<ProcessState> proc(ProcessState::self()); sp<IServi

7、四大组件之二-Service高级

一.Native Service 1>什么是Native Service 使用JNI编写,在系统启动完成之前启动的系统级服务. 2>哪些服务是Native Service ACCESSIBILITY_SERVICE ACCOUNT_SERVICE ACTIVITY_SERVICE ALARM_SERVICE AUDIO_SERVICE CLIPBOARD_SERVICE CONNECTIVITY_SERVICE DEVICE_POLICY_SERVICE DOWNLOAD_SERVICE DR

四大组件--Activity(二)

4.使用Intent传递数据(★★★★)Intent可传递的数据类型有: 八大基本类型 ,数组,ArrayList<String>, Bundle 数据捆, 序列化接口(javabean).注意:Intent传递的数据过多可能会造成跳转速度极慢甚至黑屏一会,不要用Intent传递过多的数据,会影响到应用程序的使用.下面通过一个案例来演示Intent如何传递数据,我们依然使用2.1 章节中的工程.需求:点击FirstActivity界面的按钮,将数据传递到SecondActivity,并显示传递

让我们聊聊Erlang的节点互联(二)

由于一篇Blog写太长无法发表,这里面我们将继续分析下dist.c中的setnode_3这个函数的作用和net_kernel得到连接成功之后又进行了什么操作. BIF_RETTYPE setnode_3(BIF_ALIST_3) {     BIF_RETTYPE ret;     Uint flags;     unsigned long version;     Eterm ic, oc;     Eterm *tp;     DistEntry *dep = NULL;     Port