gen_fsm 练习

最近学习四大行为模式, 跟着网上照葫芦画瓢,其中过程挺有意思的。

练习目标:

一个上锁的密码门,密码输错三次则锁定5秒,强行输入则状态锁死,输入正确密码则打开门

 

%%%-------------------------------------------------------------------
%%% @author 
%%% @copyright (C) 2020
%%% @doc
%%%     
%%% @end 
%%% Created : 2021/3/23-16-57
%%%-------------------------------------------------------------------
-module(fsm_test).
-description("fsm_test").
-behaviour(gen_fsm).
-vsn(1).
%% API
-export([init/1, start_link/1, button/1, close/2, open/2, lock/2, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3,stop/0]).

start_link(Pwd) ->
	%% {local,zy_test_fsm} 本地状态机名称
	%% 第二参是回调模块名
	%% 第三参Pwd是init的回调参数 注册成功则 M:init(Pwd) -> {ok,StateName,StateData}
	%% 第四参是状态机选项
	gen_fsm:start_link({local, zy_test_fsm}, ?MODULE, Pwd, []).
%% gen_fsm:start_link 会通过监控树加入
%% gen_fsm:start 则是启动单独的gen_fsm进场

init(Pwd) ->
	%% {ok,StateName,StateData}
	%% StateName则为 初始状态
	%% StateData则为 内部状态
	%% {[],Pwd} 业务逻辑数据,[]=输入的密码,Pwd=正确密码
	process_flag(trap_exit,true),
	{ok, close, {[], Pwd}}.

button(InputPwd) ->
	%% 使用事件通知状态机状态改变
	%% zy_test_fsm 即状态机名称
	%% {button, InputPwd} 即消息
	%% 当zy_test_fsm状态机收到消息时会调用StateName(Event,StateData) -> {next_state,StateName1,StateData1}
	%% 因为初始状态是close,所以首次向zy_test_fsm发送{button, InputPwd}消息时,
	%% zy_test_fsm会执行zy_test_fsm:close({button, InputPwd},{[],Pwd})  {[],Pwd}是之前init时的StateData
	gen_fsm:send_event(zy_test_fsm, {button, InputPwd}).

%% send_event            当前状态下处理该事件 -> StateName(Event,StateData)
%% send_all_state_event  所有状态下该事件的处理方式都是一样的 -> handle_event(NewState, _OldState, StateData)



close({button, InputPwd}, {OldInput, Pwd}) ->
	if
		InputPwd =:= Pwd ->
			io:format("密码正确! ~n", []),
			{next_state, open, {[], Pwd}}; %% 密码正确开门
		length(OldInput) >= 2 ->
			io:format("密码已经输错三次了,已被锁定5秒. ~n", []),
			{next_state, lock, {OldInput, Pwd}, 5000}; %% 输错三次了,超时5秒后执行lock状态的timeout,执行解锁,再次期间有消息进入则不会执行timeout
		true ->
			%% 密码不正确没满足三次,历史输入存进数据
			io:format("密码错误! ~n", []),
			{next_state, close, {[InputPwd | OldInput], Pwd}}
	end.

lock({button, _InputPwd}, {OldInput, Pwd}) ->
	io:format("状态被锁定 ~n", []),
	{next_state, lock, {OldInput, Pwd}};

%% 输错密码5秒超时后执行的解锁
lock(timeout, {_, Pwd}) ->
	io:format("5秒已过,可以继续输入密码了 ~n", []),
	{next_state, close, {[], Pwd}}.

%% 开门
open({button, _InputPwd}, {_, Pwd}) ->
	io:format("开门了.. ~n", []),
	{next_state, open, {[], Pwd}}.

stop() ->
	gen_fsm:send_all_state_event(zy_test_fsm, stop).

handle_event(stop, _StateName, Data) ->
	{stop, normal, Data};
handle_event(NewState, StateName, Data) ->
	io:format("handle_event...~p ~n",[{NewState,StateName}]),
	{next_state, StateName, Data}.

handle_sync_event(NewState, From, StateName, Data) ->
	io:format("handle_sync_event, for process: ~p... ~n", [{NewState, From, StateName, Data}]),
	{next_state, StateName, Data}.

handle_info(Info, StateName, Data) ->
	io:format("handle_info...~p~n",[{Info, StateName, Data}]),
	{next_state, StateName, Data}.


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

 

1> fsm_test:start_link(banana). %% <-新建一个密码为banana的开门状态机
{ok,<0.5523.0>}
2> fsm_test:button(hasaki).    
密码错误! 
ok
3> fsm_test:button(hasaki).
密码错误! 
ok
4> fsm_test:button(hasaki).     %% <-注意这里错了三次后,再次输入错误密码则锁死,需要等5秒让它自己重置次数
密码已经输错三次了,已被锁定5秒. 
ok
5秒已过,可以继续输入密码了.        %% <-触发timeout超时,可以继续输密码了 
5> fsm_test:button(hasaki).    
密码错误! 
ok
6> fsm_test:button(hasaki).
密码错误! 
ok
7> fsm_test:button(hasaki).
密码已经输错三次了,已被锁定5秒. 
ok
8> fsm_test:button(hasaki).   %% <-5秒超时内有了新消息,无法触发timeout,状态被锁死
状态被锁定
ok

个人感觉挺好玩,就是控制超时还是用自己的业务逻辑控制比较好,这里的timeout机制感觉大部分情况都不太适合。

其次,gen_fsm已经被官方建议替换为 gen_statem

 

参考了这篇文章:https://blog.csdn.net/xyj0663/article/details/8510488 是转载的,原博文被删了,讲的可以

不过我看他的这篇转载文章应该参考的https://www.iteye.com/blog/cryolite-1451070 这篇文章

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值