gen_event学习
gen_event 是通用的事件处理行为。
描述:
一个实现事件处理功能的行为模块。OTP事件处理模块包括一个通用的事件管理,负责处理可以动态添加、删除的任意数量的事件。用这个模块实现的事件管理器会有一组标准的功能接口,包括跟踪和错误报告功能。它也将融入OTP 监督树中,请参考OTP设计原则了解更多信息。
每个事件处理以回调方式来实现,回调需要导出一组预先设计好的函数,行为函数和回调函数的关系如下:
gen_event module Callback module ---------------- ---------------
%%作为监督树的一部分来创建事件管理器 gen_event:start_link -----> -
%%添加事件 gen_event:add_handler gen_event:add_sup_handler -----> Module:init/1
%%事件通知(同步,异步)当事件管理器收到通知后,所有安装的事件均会得到通知。 gen_event:notify gen_event:sync_notify -----> Module:handle_event/2
%%同步调用 gen_event:call -----> Module:handle_call/2 - -----> Module:handle_info/2
%%事件删除 gen_event:delete_handler -----> Module:terminate/2
%%时间管理起中替换现有事件 gen_event:swap_handler gen_event:swap_sup_handler -----> Module1:terminate/2 Module2:init/1
%%返回事件管理中的事件列表 gen_event:which_handlers -----> -
%%停止事件管理器 gen_event:stop -----> Module:terminate/2 - -----> Module:code_change/3
由于每个事件处理模块都是回调模块,一个事件管理器可以动态添加、删除多个回调模块,因此gen_event比其它behaviours对回调模块的错误更tolerant。如果注册的回调函数失败(返回Reason),时间管理器不会失败,它会调用
Module:terminate/2,传参数:{error,{'EXIT',Reason}},其它的事件不会受到任何影响。
注意:时间管理期器会自动捕获exit信号。
gen_event进程可以进入休眠状态,如果回调函数显示返回hibernate。如果服务器预计会空闲较长时间,但是这个特性要慎用。休眠意味着至少有2个垃圾收集(休眠和唤醒不久后)。一个繁忙的事件处理器在处理多个事件间处理的事情不是你想要的。
还需要注意的是,涉及到多个事件处理,一个事件返回hibernate就足够了,因为这回导致事件管理器进入休眠状态。
除非有特殊说明,这个模块中的所有均会失败,若果指定的管理器不存在,或者参数错误。
废话太多了,上手操作吧:
功能:创建一个error_man,注册两个event, terminal_logger和file_logger,用于打印信息到终端和文件。
上代码:
- 终端event处理
1 -module(terminal_logger). 2 -behaviour(gen_event). 3 4 -export([init/1, handle_event/2, terminate/2]). 5 6 init(_Args) -> 7 {ok, []}. 8 9 handle_event(ErrorMsg, State) -> 10 io:format("***Error*** ~p~n", [ErrorMsg]), 11 {ok, State}. 12 13 terminate(_Args, _State) -> 14 ok.
- 文件event处理
1 -module(file_logger). 2 -behaviour(gen_event). 3 4 -export([init/1, handle_event/2, terminate/2]). 5 6 init(File) -> 7 case file:open(File, write) of 8 {ok, Fd}-> 9 {ok, Fd}; 10 {_, Reason}-> 11 io:format("open file:~p failed:~p~n", [File, Reason]), 12 {error, Reason} 13 end. 14 15 16 handle_event(ErrorMsg, Fd) -> 17 io:format(Fd, "***Error*** ~p~n", [ErrorMsg]), 18 {ok, Fd}. 19 20 terminate(_Args, Fd) -> 21 file:close(Fd).
操作过程:
启动:
Eshell V6.0 (abort with ^G)
1> c(terminal_logger).
terminal_logger.erl:2: Warning: undefined callback function code_change/3 (behaviour 'gen_event')
terminal_logger.erl:2: Warning: undefined callback function handle_call/2 (behaviour 'gen_event')
terminal_logger.erl:2: Warning: undefined callback function handle_info/2 (behaviour 'gen_event')
{ok,terminal_logger}
2> c(file_logger).
file_logger.erl:2: Warning: undefined callback function code_change/3 (behaviour 'gen_event')
file_logger.erl:2: Warning: undefined callback function handle_call/2 (behaviour 'gen_event')
file_logger.erl:2: Warning: undefined callback function handle_info/2 (behaviour 'gen_event')
{ok,file_logger}
%% 启动时间管理器,本地注册名为:error_man,如果没有注册名,则需要Pid来发送信息。
3> gen_event:start({local,error_man}).
{ok,<0.45.0>}
%%添加终端事件
4> gen_event:add_handler(error_man, terminal_logger,[]).
ok
%%添加文件事件,模拟出错:未传文件名,add_handler第三个参数,将会传到事件处理的init方法中。init必须返回{ok,Stat} 其中State存储在error_man内部中。
5> gen_event:add_handler(error_man, file_logger,[]).
open file:[] failed:enoent
{error,enoent}
%%添加事件文件为:file.log
6> gen_event:add_handler(error_man, file_logger,["file.log"]).
ok
%%模拟事件"this is a event notify", 期望结果:终端输出信息,同时文件也需要输出信息
7> gen_event:notify(error_man, "this is a event notify").
ok
***Error*** "this is a event notify"
%% 查看文件:
work@nobody-Lenovo:~/test_erl$ tailf file.log
***Error*** "this is a event notify"
%%结果和我们的预期一致
%% 删除终端事件
8> gen_event:delete_handler(error_man, terminal_logger, []).
ok
%%模拟事件"this is a event notify again",预期终端不会打印信息,文件会打印,
9> gen_event:notify(error_man, "this is a event notify again").
ok
%%确认文件:
work@nobody-Lenovo:~/test_erl$ tailf file.log
***Error*** "this is a event notify"
***Error*** "this is a event notify again
Bingo,gen_event 基本用法就介绍到这里
参考:
官方文档:
- gen_event官方文档
- OTP_Design Principles User'Guide之gen_event(E文)
- OTP_Design Principles User'Guide之gen_event(中文)
照样一首歌曲送给大家:佛说万物生