1.簡介
Gen_fsm是一個通用的有限狀態機,它描述了這樣的一組關系:State(S) x Event(E) -> Actions(A),State(S')這個關系意味着:如果在S狀態下發生事件E,將執行動作A並返回狀態S'.對於一個FSM實現可以使用gen_fsm行為來實現,它提供了標准的接口函數和回調函數。並且,gen_fsm進程可以安裝在supervisor監控樹中。回調函數與導出函數的關系如下:
如果回調函數失敗或返回壞的值,gen_fsm將會終止。
1 gen_fsm moduleCallbackmodule 2 ----------------------------- 3 gen_fsm:start_link ----->Module:init/1 4 gen_fsm:send_event ----->Module:StateName/2 5 gen_fsm:send_all_state_event ----->Module:handle_event/3 6 gen_fsm:sync_send_event ----->Module:StateName/3 7 gen_fsm:sync_send_all_state_event ----->Module:handle_sync_event/4 8 ------>Module:handle_info/3 9 ------>Module:terminate/3 10 ------>Module:code_change/4
gen_fsm處理系統消息記錄記錄在sys模塊中(sys模塊可以用來調試gen_fsm)。
注意:gen_fsm不會主動的捕獲退出信號,必須在回調函數初始化時被指定。process_flag(trap_exit, true).
如果指定的gen_fsm不存在或給與的參數不正確,模塊里的所有函數將失敗。
如果回調函數返回'hibernate',gen_fsm進程將進入hibernate狀態。如果服務器長時間處於空閑狀態這可能非常有用。然而應該非常小心的使用這個特征,因為它意味着至少有垃圾回收器,當調用一個忙碌的狀態機時,你不能做你想做的事情(會因為垃圾回收而導致狀態機停滯)。
2.函數
2.1導出函數
start_link(Module, Args, Options) -> Result
start_link(FsmName, Module, Args, Options) -> Result
FsmName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}Options = [Option]Option = {debug,Dbgs} | {timeout,Time} | {spawn_opt,SOpts}Dbgs = [Dbg]Dbg = trace | log | statistics| {log_to_file,FileName} | {install,{Func,FuncState}}SOpts = [SOpt]SOpt - see erlang:spawn_opt/2,3,4,5Result = {ok,Pid} | ignore | {error,Error}Error = {already_started,Pid} | term()
用於創建一個gen_fsm進程,可以作為監控樹的一部分。可以被監控樹直接或間接的調用,將確保gen_fsm被鏈接到監控樹。gen_fsm進程調用Module:init/1來初始化。為了確保同步的啟動gen_fsm,start_link/3,4直到Module:init/1返回時才返回。
如果FsmName = {local,Name},gen_fsm在本地通過register/2注冊為Name.如果FsmName = {global, GlobalName},gen_fsm在全局通過global:register_name/2注冊為GlobalName.如果FsmNam = {via, Module, ViaName},gen_fsm通過Module注冊為ViaName,同時回調模塊Module將導出函數register_name/2, unregister_name/2, whereis_name/1和send/2,其原理同global,因此{via, Module, GlobalName}是一個有效的引用。
如果沒有指定name,gen_fsm不被注冊。
Args是一個任意的項式,用於作為Module:init/1的參數。
如果option是{timeout, Time}那么gen_fsm有Time毫秒進行初始化,否則,將返回{error, timeout}來終止。
如果option是{debug, Dbgs}那么Dbgs中相應的sys函數將被調用。詳情見sys(3).
如果option是{spawn_opt, Sopts}那么通過指定的值列表來產生gen_fsm進程。詳見erlang:spawn_opt
link
|{priority, priority_level()}
|{min_heap_size, integer()>=0}
|{min_bin_vheap_size, integer()>=0}
|{fullsweep_after, integer()>=0}
如果gen_fsm被成功的創建和初始化,將返回{ok,Pid},如果已經存在指定FsName的進程,將返回{error,{alredy_started, Pid}}.如果Module:init/1因為Reason失敗,將返回{error,Reason}.如果Module:init/1返回{stop, Reason}或ignore,並且相應的返回{error,Reason}或ignore.start(Module, Args, Options) -> Result
start(FsmName, Module, Args, Options) -> Result
FsmName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}Options = [Option]Option = {debug,Dbgs} | {timeout,Time} | {spawn_opt,SOpts}Dbgs = [Dbg]Dbg = trace | log | statistics| {log_to_file,FileName} | {install,{Func,FuncState}}SOpts = [term()]Result = {ok,Pid} | ignore | {error,Error}Error = {already_started,Pid} | term()
用於創建一個獨立gen_fsm進程,並不是監控樹的一部分。參數描述參見start_link/3,4.send_event(FsmRef, Event) -> ok
FsmRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
向gen_fsm的FsmRef發送一個異步事件,並立即返回ok.gen_fsm將調用Module:StateName/2來處理事件,StateName為當前gen_fsm的狀態。
FsmRef可能是:
- Pid
- Name,本地注冊的gen_fsm
- {Name,Node}另一個節點上本地注冊gen_fsm
- {global,GlobalName}全局注冊的gen_fsm
- {via,Module,ViaName}通過指定的進程注冊的gen_fsm
Event是一個任意的項式,將被作為參數傳遞給Module:StateName/2處理。
send_all_state_event(FsmRef, Event) -> ok
FsmRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
向gen_fsm的FsmRef發送一個異步事件並立即返回ok.這個gen_fsm將調用Module:handle_event/3來處理這個事件。參數描述參見send_event/2.send_event和send_all_state_event的不同是處理事件的回調函數不同,該函數是在任何狀態下都以同樣的方式處理事件,只有一個handle_event子句來處理事件。
sync_send_event(FsmRef, Event) -> Reply
sync_send_event(FsmRef, Event, Timeout) -> Reply
FsmRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()Timeout = int()>0 | infinityReply = term()向gen_fsm的FsmRef發送一個事件然后等待直到reply返回或發生超時,將調用Module:StateName/3來處理事件,StateName是gen_fsm 當前的狀態名。參數描述參見send_event/2.Timeout是一個等待reply返回大於0指定的毫秒數,或指定為infnity.默認是5000毫秒。如果指定時間內未返回reply函數調用失敗。返回值Reply是回調函數Module:StateName/3返回的。
sync_send_all_state_event(FsmRef, Event) -> Reply
sync_send_all_state_event(FsmRef, Event, Timeout) -> Reply
FsmRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()Timeout = int()>0 | infinity向gen_fsm的FsmRef發送一個事件然后等待reply的返回或超時。gen_fsm將調用Module:handle_sync_event/4來處理。參數描述參見send_event/2和sync_send_event/3.
reply(Caller, Reply) -> true
Caller - see below該函數用於通過gen_fsm向指定的客戶端發送返回值,例如sync_send_event/2,3或sync_send_all_state/2,3當其回調函數Module:StateName/3或Module:handle_sync_event/4不能返回值時。Caller是客戶端的From,Reply是一個任意的項式,他將返回給sync_send_event/2,3或sync_send_all_state/2,3.
send_event_after(Time, Event) -> Ref
Time = integer()Ref = reference()用於在gen_fsm內部發送一個延遲事件,在Time時間后將觸發。立即返回一個引用,它可以用cancel_timer/1來取消該延遲事件。gen_fsm將調用Module:StateName/2處理該事件,StateName是當gen_fsm在延遲時間到時延遲事件被發送時的狀態名字。Event是任意類型的項式將作為參數傳遞給Module:StateName/2.
start_timer(Time, Msg) -> Ref
Time = integer()Ref = reference()在gen_fsm內部發送一個超時的事件,在Time事件時執行,並立刻返回他的引用,可以通過cancel_timer/1來取消該方法。gen_fsm將調用Module:StateName/2處理該事件,StateName是發送該超時事件的狀態。Msg是任意類型項式的超時信息{timeout, Ref, Msg},將作為Module:StateName/2的參數信息。
cancel_timer(Ref) -> RemainingTime | false
Ref = reference()RemainingTime = integer()gen_fsm調用該函數來取消名稱為Ref的內部定時器。引用Ref是由send_event_after/2和start_timer/2返回。如果定時器已經超時,但是並沒用事件發送,他被作為超時事件被取消,因此該函數的返回不會有錯誤的計時器事件。如果Ref指定的是活躍的事件計時器,將返回剩余的時間直到計時器終止前,否則返回false.
enter_loop(Module, Options, StateName, StateData)
enter_loop(Module, Options, StateName, StateData, FsmName)
enter_loop(Module, Options, StateName, StateData, Timeout)
enter_loop(Module, Options, StateName, StateData, FsmName, Timeout)
Options = [Option]Option = {debug,Dbgs}Dbgs = [Dbg]Dbg = trace | log | statistics| {log_to_file,FileName} | {install,{Func,FuncState}}FsmName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}Timeout = int() | infinity使一個存在的進程變成gen_fsm.沒有返回,取而代之的是,調用進程將進入gen_fsm的接受循環成為一個gen_fsm進程。進程必須使用proc_lib來啟動該函數.因此通過該函數進行初始化和名字注冊。當要進行一個比gen_fsm更要復雜的初始化過程時,這個函數非常有用。Module, Options and FsmName與start_link中的意義一樣。然而,如果FsmName被指定,在這個函數調用前,這個進程必須被相應地注冊。StateName,StateDate and Timeout的意義與Module:init/1一樣。同時,回調模塊Module不需要導出init/1方法。失敗情況:通過proc_lib啟動函數不能啟動該進程時;不能注冊指定的FsmName時。
2.2回調函數
Module:init(Args) -> Result
Result = {ok,StateName,StateData} | {ok,StateName,StateData,Timeout}| {ok,StateName,StateData,hibernate}| {stop,Reason} | ignoreTimeout = int()>0 | infinity每當gen_fsm使用gen_fsm:start[_link]/3,4開始時,通過新進程調用該函數來初始化。Args是啟動函數提供的參數。如果初始化成功,這個函數應該返回{ok, StateName, SateData[,Timeout | hibernate]},SateName是初始的狀態名,StateData是初始的狀態數據。如果提供了一個整數的超時值,在Timeout時間內如果沒有接受到事件或消息那么超時將發生。超時的表現是通過一個原子timeout被回調函數Modules:StateName/2處理。其默認值是infinity,表示會一直等待下去。如果hibernate被指定,那么進程將進入hibernate狀態等待下一個消息的到達。如果在初始化期間發生了一些錯誤,函數應該返回{stop, Reason}或ignore.
Module:StateName(Event, StateData) -> Result
Event = timeout | term()Result = {next_state,NextStateName,NewStateData}
| {next_state,NextStateName,NewStateData,Timeout}| {next_state,NextStateName,NewStateData,hibernate}| {stop,Reason,NewStateData}Timeout = int()>0 | infinity這是一個每個可能狀態名的一個函數實例。每當gen_fsm接收到由gen_fsm:send_event/2發送過來的事件時,當前狀態名StateName的函數實例將被調用來處理這個事件。同時他也處理超時事件。Event要么是超時發生時的原子timeout,或send_event/2傳遞過來的。如果該函數返回{next_state, NextStateName, NewStateData [, Timeout | hibernate]}時,gen_fsm將繼續執行同時轉變到新的狀態NextStateName並更新數據NewStateDate.hibernate和Timeout的描述詳見Module:init/1.如果返回{stop, Reason, NewStateName},gen_fsm將調用Module:terminate(Reason, NewStateData)終止。
Module:handle_event(Event, StateName, StateData) -> Result
Result = {next_state,NextStateName,NewStateData}
| {next_state,NextStateName,NewStateData,Timeout}| {next_state,NextStateName,NewStateData,hibernate}| {stop,Reason,NewStateData}Timeout = int()>0 | infinity每當gen_fsm接收到由gen_fsm:send_all_state_event/2發送過來的事件時,這個函數將被調用來處理該事件。參數描述以及返回值描述參見Module:StateName/2.
Module:StateName(Event, From, StateData) -> ResultFrom = {pid(),Tag}Result = {reply,Reply,NextStateName,NewStateData}| {reply,Reply,NextStateName,NewStateData,Timeout}| {reply,Reply,NextStateName,NewStateData,hibernate}| {next_state,NextStateName,NewStateData}| {next_state,NextStateName,NewStateData,Timeout}| {next_state,NextStateName,NewStateData,hibernate}| {stop,Reason,Reply,NewStateData} | {stop,Reason,NewStateData}Timeout = int()>0 | infinityReason = normal | term()這是一個每個可能狀態名的一個函數實例。每當gen_fsm接收到由gen_fsm:sync_send_event/2發送過來的事件時,當前狀態名StateName的函數實例將被調用來處理這個事件。
Event是由sync_send_event的Event參數提供。From是一個元組{Pid, Tag},Pid是調用sync_send_event/2,3的進程的Pid,Tag是他的唯一標識。當函數返回{reply, Reply, NextStateName, NewStateName [, Timeout | hibernate]}時,gen_fsm將繼續執行同時轉變到狀態NextStateName,並跟新值到NewStateData.Timeout和hibernate的描述參見Module:init/1.當函數返回{next_state, NextStateName, NewStateData [, Timeout | hibernate]}時,gen_fsm將繼續執行,同時轉變到狀態NextStateName並更數據到NewStateName.同時必須現實的調用gen_fsm:reply/2來返回數據reply給From.如果函數返回{stop, Reason, Reply, NewStateData}或{stop, Reason, NewStateNameData}(該情況必須明確的使用gen_fsm:reply/2返回reply)時,gen_fsm將調用Module:terminate(Reason NewStateName)終止。Module:handle_sync_event(Event, From, StateName, StateData) -> Result
Result = {reply,Reply,NextStateName,NewStateData}| {reply,Reply,NextStateName,NewStateData,Timeout}| {reply,Reply,NextStateName,NewStateData,hibernate}| {next_state,NextStateName,NewStateData}| {next_state,NextStateName,NewStateData,Timeout}| {next_state,NextStateName,NewStateData,hibernate}| {stop,Reason,Reply,NewStateData} | {stop,Reason,NewStateData}Timeout = int()>0 | infinity每當gen_fsm接受到由gen_fsm:sync_send_all_state_event/2,3發送過來的事件時,這個函數被調用來處理該事件。參數詳見Module:StateName/3描述。
Module:handle_info(Info, StateName, StateData) -> Result
Result = {next_state,NextStateName,NewStateData}| {next_state,NextStateName,NewStateData,Timeout}| {next_state,NextStateName,NewStateData,hibernate}| {stop,Reason,NewStateData}當gen_fsm接收到其他消息時(除出去同步或異步事件以及系統消息),該函數被調用來處理該消息。其他參數以及返回值描述參見Module:StateName/2.
Module:terminate(Reason, StateName, StateData)
Reason = normal | shutdown | {shutdown,term()} | term()這個函數在gen_fsm終止時被調用。它的作用與Module:init/1相對並可以做一些必要的清理。當他返回gen_fsm因為Reason終止的原因,返回值將被忽略。如果Reason是term指定的停止原因,StateName就是當前狀態,StateData就是當前狀態數據。如果Reason取決於gen_fsm為什么被終止。如果他是因為另一個回調函數返回了stop元組{stop,...},Reason將在那個元組里被指定。如果由於失敗導致,Reason是一個錯誤原因。如果gen_fsm是監控樹的一部分,被他的監控樹所終止,Reason = shutdown 要滿足以下條件:
- gen_fsm被設置成可捕獲退出信號
- 監控樹的關閉策略是指定Timeout而不是brutal_kill
甚至如果gen_fsm並不是監控樹的一部分,如果從其父進程接收到'EXIT'信號這個函數也會被調用,Reason就是'EXIT'信息。否則,gen_fsm將立即終止。注意:非 normal , shutdown , or {shutdown,Term}的任何其他原因,如gen_fsm的終止是因為error,這些錯誤將由error_logger:format/2進行報告。
Module:code_change(OldVsn, StateName, StateData, Extra) -> {ok, NextStateName, NewStateData}
OldVsn = Vsn | {down, Vsn}關於代碼的熱更新,后續專題會繼續介紹。
3.實踐
3.1 啟動一個gen_fsm
1 start_link(Code)-> 2 %%gen_fsm:start_link({local,?SERVER},?MODULE, lists:reverse(Code),[]). 3 gen_fsm:start({local,?SERVER},?MODULE, lists:reverse(Code),[{timeout,1000}]). 4 -spec(init(Args:: term())-> 5 {ok,StateName:: atom(),StateData::#state{}} | 6 {ok,StateName:: atom(),StateData::#state{}, timeout() | hibernate} | 7 {stop,Reason:: term()}| ignore). 8 init(Code)-> 9 %%timer:sleep(2000), 10 %%{stop,Code}. 11 %%ignore. 12 {ok, unlock,{[],Code}}.當調用gen_fsm:start開啟fsm服務器事,將回調Modules:init/1進行初始化,並初始化狀態unlock,以及狀態值{[],Code}.
3.2 發送一個事件
1 button(Digital)-> 2 gen_fsm:send_event(?SERVER,{button,Digital}). 3 unlock({button,Digital},{SoFar,Code})-> 4 case[Digital|SoFar] of 5 Code-> 6 io:format("Pass OK!~n"), 7 do_unlock(), 8 {next_state, open,{[],Code},3000}; 9 InCompletwhen length(InComplet)< length(Code)-> 10 io:format("Input:~p,InComplet~p,Code:~p~n",[Digital,[Digital|SoFar],Code]), 11 {next_state, unlock,{InComplet,Code}}; 12 _Worng-> 13 io:format("ReInput Password!~n"), 14 {next_state, unlock,{[],Code}} 15 end. 16 open(timeout,State)-> 17 do_lock(), 18 timer:sleep(1000), 19 %%{stop, normal,State}. 20 {next_state, unlock,State}.發送一個異步事件,然后當前狀態對事件進行處理,根據一定的條件進行狀態變更,返回{stop, Reason, State}將調用Module:terminate/2進行終止.
1 sync_button()-> 2 gen_fsm:sync_send_event(?SERVER,open). 3 %%gen_fsm:sync_send_event(?SERVER,close,1000). 4 5 unlock(open,_From,State)-> 6 do_unlock(), 7 io:format("~p~p~n",[_From,State]), 8 gen_fsm:reply(_From,{ok,"Successfly!"}), 9 %%gen_fsm:reply(_From,normal),{stop, normal,State}. 10 %%{stop, normal, normal,State}. 11 {next_state, close,State,1000}. 12 %%Reply= ok, 13 %%{reply,Reply, open,State,3000}. 14 close(timeout ,State)-> 15 do_lock(), 16 io:format("~p~p~n",["timeout",State]), 17 {next_state, open,State,1000}.發送一個同步事件,當前狀態進行處理以后等待返回,返回{reply, Reply, StateName, State},Reply將返回給調用端。返回{next_state, State, State},那么必須在返回之前調用gen_fsm:reply/2顯示的返回給調用端。
3.3 發送一個所有狀態都能接受的事件
1 send(Data)-> 2 gen_fsm:send_all_state_event(?SERVER,{send,Data}). 3 -spec(handle_event(Event:: term(),StateName:: atom(), 4 StateData::#state{}) -> 5 {next_state,NextStateName:: atom(),NewStateData::#state{}} | 6 {next_state,NextStateName:: atom(),NewStateData::#state{}, 7 timeout()| hibernate}| 8 {stop,Reason:: term(),NewStateData::#state{}}). 9 handle_event({send,Data},StateName,State)-> 10 io:format("Send Data:~p,StateName:~p,State,~p~n",[Data,StateName,State]), 11 {next_state,StateName,State}.向所有的狀態發送一個異步的事件,並由handle_event/3進行處理。
1 sync_send(Data)-> 2 Res= gen_fsm:sync_send_all_state_event(?SERVER,{send,Data}), 3 io:format("~p~n",[Res]). 4 handle_sync_event({send,Data},_From,StateName,State)-> 5 timer:sleep(2000), 6 gen_fsm:reply(_From,{Data,StateName,State,_From}), 7 {next_state, open,State,1000}. 8 %%{reply,{Data,StateName,State,_From}, open,State,1000}. 9 %%{reply,{Data,StateName,State,_From},StateName,State}.向所有的狀態發送一個同步事件,並由handle_sync_event/4進行處理,並向調用端返回Reply,如果返回{next_state, StateName, State}必須顯示調用gen_fsm:reply/2向調用端返回。
3.4 開啟一個定時器
1 unlock(open,_From,State)-> 2 do_unlock(), 3 %%gen_fsm:send_event_after(2500,{button,48}), 4 Ref= gen_fsm:start_timer(1500,"start_timer"), 5 io:format("~p~p~n",[_From,State]), 6 gen_fsm:reply(_From,{ok,"Successfly!"}), 7 proc_lib:spawn(fun()-> 8 timer:sleep(2000), 9 Time= gen_fsm:cancel_timer(Ref), 10 io:format("RemainTime:~p~n",[Time]) 11 end), 12 %%gen_fsm:reply(_From,normal),{stop, normal,State}. 13 %%{stop, normal, normal,State}. 14 {next_state, close,State,1000}. 15 %%Reply= ok, 16 %%{reply,Reply, open,State,3000}.send_event_after/2表示在指定時間后發送事件,start_timer/2表示在指定事件后發送一個超時事件{ timeout, Ref, Msg },但同時可以用cancel_timer/1來結束定時器。
3.5 將普通進程轉變為gen_fsm進程
1 -spec(start_link()->{ok, pid()}| ignore |{error,Reason:: term()}). 2 start_link()-> 3 io:format("start_link~n"), 4 proc_lib:start_link(?MODULE, start_loop,[self()]). 5 %%gen_fsm:start_link({local,?SERVER},?MODULE,[],[]). 6 start_loop(Person)-> 7 io:format("start_loop~n"), 8 proc_lib:init_ack(Person,self()), 9 register(?MODULE,self()), 10 gen_fsm:enter_loop(?MODULE,[], state_name,#state{}, {local, ?MODULE}).通過gen_fsm:enter_loop將一個普通的進程轉變為gen_fsm進程。同時不必導出Module:init/1回調函數。適用於比init:/1要更復雜的初始化的處理。
4.總結
gen_fsm實現了通用的有限狀態機(FSM),對與在多種狀態間的變更處理非常的有用, 比如文本解析,模式匹配、游戲邏輯等等方面的處理都是它的強項,所以這個behaviour非常之重要,gen_fsm提供了簡便的接口與回調函數來構建有限狀態機,根據不同的返回參數在不同的狀態間進行切換,也具備了同步或異步事件的處理。
優秀的代碼是藝術品,它需要精雕細琢!