Erlang OTP行为模式behaviour——gen_event

匆匆过客 提交于 2020-03-09 18:26:49

说明

最近笔者在学习otp行为模式,在查阅相关资料时发现网上有关erlang的资料不是很多,也不够详细。在参考官方文档以及其他作者的文章后,决定写这篇博客。算是自己对知识的复习,也为诸君提供一些参考。

1.gen_event 简介

Gen_event实现了通用事件处理,通过其提供的标准接口方法以及回调函数,在OTP里面的事件处理模块是由一块通用的事件管理器和任意数量的事件处理器,并且这些事件处理器可以动态的添加和删除。一个事件可以用来记录error,alarm,info, warning等信息。一个事件管理器可以安装一个或多个事件处理器,当一个事件管理器接受到一个事件的通知时,这个事件将会被所有的已安装的事件处理器依次处理。

事件管理器是作为一个进程实现的,每个事件处理器都是一个回调模块。且事件管理器实质上是一个{Module,State} 组成的列表,Module是事件处理器,State为该事件处理器内部状态。

2.事件处理器

不多废话,直接上代码,以下为一个事件处理器标准模板,具体函数将在后面做出详细解释。事件处理器可以有多个。

-module(info_logger).

-behaviour(gen_event).

%% gen_event callbacks
-export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2,
  code_change/3]).

-define(SERVER, ?MODULE).

%%%===================================================================
%%% gen_event callbacks
%%%===================================================================
init(State) ->
  io:format("**** info_logger init success ****"),
  {ok,State }.

handle_event(Event, State) ->
  io:format("**** info_logger handle_event **** ~n Event =~p~n",[Event]),
  {ok, State}.

handle_call(Request, State) ->
  io:format("**** info_logger handle_call **** ~n Request =~p~n",[Request]),
  Reply = Request,
  {ok, Reply, State}.

handle_info(_Info, State ) ->
  {ok, State}.

terminate(Args, _State) ->
  io:format("**** info_logger exit success **** ~n Args =~p~n ",[Args]),
  ok.

code_change(_OldVsn, State , _Extra) ->
  {ok, State}.

2.1 回调函数

  • Module:init(InitArgs) -> {ok,State} | {ok,State,hibernate} | {error,Reason}

InitArgs = Args | {Args,Term}
每当一个新的事件处理器添加到一个事件管理器时,这个函数将被调用来初始化这个事件处理器。
如果通过调用gen_event:add_handler/3或gen_event:add_sup_handler/3来添加事件处理器。InitArgs以Args作为参数。
如果事件处理器是通过调用gen_event:swap_handler/3或gen_event:swap_sup_handler来替换另一个事件处理器,InitArgs以{Args,Term}作为参数。
如果初始化成功,将返回{ok,State} | {ok,State,hibernate}.
如果返回{ok,State,hibernate},事件管理器将进入’hibernate’状态,并等待下一个事件发生。

  • Module:handle_event(Event, State) -> Result

Result = {ok,NewState} | {ok,NewState,hibernate}
| {swap_handler,Args1,NewState,Handler2,Args2} | remove_handler
Handler2 = Module2 | {Module2,Id}
每当事件管理器接收到通过gen_event:notify/2或gen_event:sync_notify/2发送过来的事件时,每个安装的事件处理器将调用该方法处理事件。
Event和notify/sync_notify的参数匹配。
如果这个函数返回{ok, NewState}或{ok, NewState, hibernate}该事件处理器保持在事件管理器内部,并更新内部状态。
如果任一事件管理器返回的是{ok,NewState,hibernate},事件管理器将进入’hibernate’状态,等待下一个事件的产生。
如果函数返回{swap_handler,Args1,NewState,Handler2,Args2}该事件处理器将被Handler2替换,通过调用Module:terminate(Args1,NewState)和Module2:init({Args2,Term})进行替换。详情参见gen_event:swap_handler/3.
如果该函数返回remove_handler,该事件管理器将被通过调用Module:terminate(remove_handler,State)删除。

  • Module:handle_call(Request, State) -> Result

Result = {ok,Reply,NewState} | {ok,Reply,NewState,hibernate}
| {swap_handler,Reply,Args1,NewState,Handler2,Args2}
| {remove_handler, Reply}
Handler2 = Module2 | {Module2,Id}
每当一个事件管理器接收到来自gen_event:call/3,4的请求时,这个函数将被指定的事件处理器调用来处理这个请求。
该函数的返回值参见handle_event/2,同时包含了一个任意的数据结构Reply,它将返回给调用端。

  • Module:handle_info(Info, State) -> Result

Result = {ok,NewState} | {ok,NewState,hibernate}
| {swap_handler,Args1,NewState,Handler2,Args2} | remove_handler
Handler2 = Module2 | {Module2,Id}
当一个事件管理器接收到其他信息(除event,synchronous request,system message)每个安装的事件处理器都会调用该函数处理这个消息。
返回参数详情见handle_event/2.

  • Module:terminate(Arg, State) -> term()

Arg = Args | {stop,Reason} | stop | remove_handler
| {error,{‘EXIT’,Reason}} | {error,Term}
Args = Reason = Term = term()
每当一个事件处理器从事件管理器中被删除时,该函数被调用。它与Module:init/1相反用来做一些清理工作。
如果该事件管理器是调用gen_event:delete_handler, gen_event:swap_handler/3 or gen_event:swap_sup_handler/3来删除,Arg就是他们调用时的参数信息Args.
如果事件处理器相监控连接的进程由于Reason终止时,Arg ={stop, Reason}.
如果由于事件管理器终止而终止事件处理器时,Arg = stop.
如果事件管理器是监控树的一部分并被监控树按顺序终止。甚至它不是监控树的一部分,如过它接受到来自父进程的退出信号’EXIT’都将被终止。
如果时间处理器被删除由于另一个回调函数返回了remove_handler or {remove_handler,Reply},Arg = remove_handler.
如果事件处理器被删除是因为回调函数返回了一个意外值Term,Arg={error,Term};如果回调函数失败,Arg={error,{‘EXIT’,Reason}}.
该函数将返回任意类型的值。如果事件处理器被删除是由于调用gen_event:delete_handler,那个函数的返回值就是这个函数的返回值。如果事件处理器被另一个时间处理器替换,返回值将被作为另一个事件处理器的初始化参数。否则,返回值将被忽略。

  • Module:code_change(OldVsn, State, Extra) -> {ok, NewState}

2.2 gen_event导出函数

  • start_link() -> Result
  • start_link(EventMgrName) -> Result

EventMgrName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}
Result = {ok,Pid} | {error,{already_started,Pid}}

监控树可以通过调用该方法创建一个事件管理器进程作为一颗监控树的一部分,并连接到监控树。

如果EventMgrName={local,Name},事件管理器在本地被注册为Name通过register/2. 如果EventMgrName={global,GlobalName},事件管理器将在全局被注册为GlobalName通过global:register_name/2.如果没有指定名字,事件管理器将不会被注册.如果EventMgrName={via,Module,ViaName},事件管理器将通过Module被注册为ViaName.Module模块应该导出register_name/2, unregister_name/1, whereis_name/1 and send/2,它的原理与global相似。

如果事件管理器被成功创建将返回{ok,Pid},如果指定名字的事件管理器已经存在将返回{error,{already_started,Pid}}

  • start() -> Result
  • start(EventMgrName) -> Result

EventMgrName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}
Result = {ok,Pid} | {error,{already_started,Pid}}
创建一个独立与监控树的事件管理器。其参数与start_link/0,1一样。

  • add_handler(EventMgrRef, Handler, Args) -> Result

EventMgr = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
Handler = Module | {Module,Id}
Result = ok | {‘EXIT’,Reason} | term()
向事件管理器EventMgrRef添加一个事件处理器,事件管理器将通过回调Module:init/1初始化事件处理器和它的内部状态。
EventMgrRef可以是:

  • pid
  • Name,本地注册的事件管理器名字
  • {Name, Node},在本地另一个节点注册的事件管理器名字
  • {global,GlobalName},全局注册的事件管理器名字
  • {via,Module,ViaName},通过指定模块注册的事件管理器的名字

Handler是一个gen_event回调模块的名字或元组{Module, Id},元组{Module, Id}是一个特别的具有身份Id的gen_event回调模块,用来处理当事件处理器是同样一块gen_event回调模块的情况。
Args是任意的数据类型,用来作为Module:init/1的参数。
如果Module:init/1返回一个正确的值表明成功添加事件处理器,事件管理器的添加函数将返回ok.如果Module:init/1因为Reason失败或返回{error, Reason},这个事件处理器将被忽略并且该函数返回{‘EXIT’,Reason}或{error, Reason}.

  • add_sup_handler(EventMgrRef, Handler, Args) -> Result

EventMgr = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
Handler = Module | {Module,Id}
Result = ok | {‘EXIT’,Reason} | term()
与add_handler/3同样的方式添加一个新的事件处理器,并且与调用用进程间建立监控。
如果调用进程因为原因Reason终止,事件管理器将回调Module:terminate/2删除该事件处理器,并以{stop, Reason}作为参数。
如果事件处理器被删除,事件管理器将发送一个消息{gen_event_EXIT,Handler,Reason}给调用进程。Reason可能是:
normal,如果事件处理器通过调用delete_handler/3被删除
shutdown,如果事件处理器因为事件管理器终止而被移除
{swapped,NewHandler,Pid},如果事件处理器被另一个事件处理器NewHandler替换通过swap_handler或swap_sup_handler/3.
Term,事件处理器因为error而被删除,Trem取决于错误类型
其他参数的描述与add_handler/3中的描述一样。

  • notify(EventMgrRef, Event) -> ok
  • sync_notify(EventMgrRef, Event) -> ok

EventMgrRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
Event = term()
发送一个事件通知给事件处理器EventMgrRef.这个事件管理器将调用每个安装事件处理器的Module:handle_event/2处理这个事件。
notify/2是异步的事件通知被发送后将立即返回。sync_notify/2是同步的当事件被所有事件处理器处理后将返回ok.
对于EventMgrRef的描述参看add_handler/3.
Event是任意类型的数据格式,将被Module:handle_event/2处理。

  • call(EventMgrRef, Handler, Request) -> Result
  • call(EventMgrRef, Handler, Request, Timeout) -> Result

EventMgrRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
Handler = Module | {Module,Id}
Timeout = int()>0 | infinity
Result = Reply | {error,Error}
Error = bad_module | {‘EXIT’,Reason} | term()
向事件管理器EventMgrRef的事件处理器Handler发送一个同步请求Request,然后等待返回,或直到请求超时。事件管理器将调用Module:handle_call/2处理这个请求。
EventMgrRef and Handler的描述详见add_handler/3.
请求Request是一个任意的数据结构将被Module:handle_call/2处理。
Timeout是一个等待返回的大于0的毫秒数或者是infinity(默认5000)。如果在指定时间内没有返回这函数请求失败。
返回值Reply在Module:handle_call/2中被定义。如果指定的事件处理器Handler未安装函数将返回{error,bad_module}.如果回调函数失败因为原因Reason或返回意外值Term,这个方法将返回{error,{‘EXIT’,Reason}}或{error,Term}.

  • delete_handler(EventMgrRef, Handler, Args) -> Result

EventMgrRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
Handler = Module | {Module,Id}
Result = term() | {error,module_not_found} | {‘EXIT’,Reason}
从事件管理器EventMgrRef中删除一个事件处理器Handler。事件管理器将调用Module:terminate/2来终止这个事件处理器。
对EventMgrRef and Handler的描述参见add_handler/3.
Args是一个任意类型数据结构将被传递给Module:terminate/2来处理。
返回值是Module:terminate/2的返回值。如果指定的事件处理器未安装,将返回{error,module_not_found}.如果回调函数因为Reason失败,函数将返回{‘EXIT’,Reason}.

  • swap_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result

EventMgrRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
Handler1 = Handler2 = Module | {Module,Id}
Result = ok | {error,Error}
Error = {‘EXIT’,Reason} | term()
在事件管理器EventMgrRef中,用一个新的事件处理器替换老的事件处理器。
第一个老的事件处理器被删除。事件管理器调用Module1:terminate(Args1, …)删除事件处理器并保存其返回值,Module1是Handler1的回调模块。
然后新的事件处理器Handler2被添加并通过Module2:init({Args2,Term})初始化,Module2是Handler2的回调模块,Term是Module1:terminate的返回值,是Handler1传递给Handler2的消息.
新的事件处理器将被添加,即使指定的老的事件处理器未安装(Term=error);或Module1:terminate/2由于Reason失败(Term={‘EXIT’,Reason}).老的事件处理器将被删除,即使Module2:init/1失败。
如果事件处理器Handler1与Pid间建立了监控连接,事件处理器Handler2将替代Handler1与Pid建立监控连接。
如果Module2:init/2返回一个正确的值,这个方法将返回ok.如果Module2:init/1因为Reason或返回意外值而失败这个函数将返回{error,{‘EXIT’,Reason}}或{error,Term}.

  • swap_sup_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result

EventMgrRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
Handler1 = Handler 2 = Module | {Module,Id}
Result = ok | {error,Error}
Error = {‘EXIT’,Reason} | term()
在事件管理器EventMgrRef中,用一个新的事件处理器替换老的事件处理器,同时,将Handler2与调用进程间建立监控连接。
其他参数描述参见swap_handler/3.

  • which_handlers(EventMgrRef) -> [Handler]

EventMgrRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
Handler = Module | {Module,Id}
返回事件管理器EventMgrRef中所有安装的事件处理器。
EventMgrRef and Handler的描述详见add_handler/3.

  • stop(EventMgrRef) -> ok

EventMgrRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
终止事件管理器EventMgrRef.在终止之前事件管理器将调用Module:terminate(stop, …)终止安装的事件处理器。
EventMgrRef的描述详见add_handler/3.

3.实例

3.1 启动事件管理器

启动事件管理器
通过start_link/1创建一个时间管理器,并返回一个进程标识符。由此可见,事件管理器是作为一个进程实现的。

同时终止管理器的方法为:gen_event:stop(logger).
当终止事件管理器时会调用事件处理器的Module:terminate(stop, …),来依次关闭事件处理器。
结果就不演示了。

3.2添加事件处理器

在这里插入图片描述
可以看出,当添加一个时间管理器时,程序执行了回调函数module:init/1。表明gen_event:add_handler/3 会回调module:init/1,且第三个参数会作为init/1的参数,初始化事件处理器的内部状态。

同理,删除事件处理器:gen_event:delete_handler(logger,info,[])
gen_event:delete_handler(?SERVER,myerror,[])

3.3 事件消息

在这里插入图片描述
这里请注意,当事件管理器发出一个消息时,会一次调用每个事件处理器的handler_event/2.且notify/2是异步通知,当一个事件处理器返回ok,才会接着调用下一个事件处理器。而sync_notify是同步通知,如下图
在这里插入图片描述
这里我们改一下info_logger的代码,把第一参数改为一个固定值,再看结果。

handle_event(‘ok’, State) ->
  io:format("**** info_logger handle_event **** ~n Event =~p~n",[ok]),
  {ok, State}.

在这里插入图片描述
可以看到,warn_logger正常运行,但是info_logger返回错误信息,且自动退出了。

3.4 事件交换

在这里插入图片描述
首先会调用myevent:terminate(normal_swap, …)来终止myevent事件处理器,并返回数据Term;然后调用myerror:init({[],Term})来启动myerror事件处理器。
特别注意:新的事件处理器将被添加,即使指定的老的事件处理器未安装(Term=error);或Module1:terminate/2由于Reason失败(Term={‘EXIT’,Reason}).老的事件处理器将被删除,即使Module2:init/1失败。

4.总结

通过Gen_event我们可以实现通用的事件管理器,用于记录一些消息、警告、错误的信息。gen_event提供了标准的接口和回调函数来实现事件管理器与事件处理器。事件管理器可以动态的添加替换事件处理器,也可以动态的删除事件处理器。当一个事件到达时,所有安装的事件处理器都会处理该事件信息。事件管理器具有比其他行为模块的容错能力,当一个事件处理器失败时,并不会影响其他事件处理器。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!