Erlang事件处理器gen_event

一、什么是gen_event
按照书上定义在OTP中,它由通用事件管理器进程组成,该进程具有动态添加和删除的任意数量的事件处理程序。事件可以是例如错误,警报或要记录的某些信息

简单来说,就是gen_event行为运行一个了一个事件管理进程,该进程接受消息(事件),并根据消息(事件)做对应的事件处理,而提供的对应事件处理其实就是添加的“回调函数”(事件处理器)。与gen_server不一样的是,他把回调的函数分类成多个module并称为事件处理器,需要注意的是事件处理器不是一个进程,看源码你会发现只是事件管理器进程里构造了一个记录列表,里面储存了对应的module和函数参数,必要时就调用。gen_event在游戏开发中除了在日记模块有用到,其他的地方几乎没有应用
up-a286fc4f9df6597a9e87cf0a14fca9ed793.png

二、如何使用

使用OTP事件管理器需要做四件事:

  1. 编写事件处理程序,
  2. 创建事件管理器,
  3. 向管理器添加处理程序,
  4. 通知事件管理器

up-b7fb93bbe2e860332e9c2fe4e56750d76e0.png

以erlang趣学指南的冰球比赛为案例

事件管理器

-module(curling).
-export([start_link/2, set_teams/3, add_points/3, next_round/1]).
-export([join_feed/2, leave_feed/2]).
-export([game_info/1]).
 
start_link(TeamA, TeamB) ->
%%    创建事件管理进程
    {ok, Pid} = gen_event:start_link(),
    %% 添加事件处理器 gen_event:add_handler(事件管理进程pid, 回调模块, 参数),curling_scoreboard为计分板
    gen_event:add_handler(Pid, curling_scoreboard, []),
    %% accumulator为累加器
    gen_event:add_handler(Pid, curling_accumulator, []),
    set_teams(Pid, TeamA, TeamB),
    {ok, Pid}.
 
%%设置队伍
set_teams(Pid, TeamA, TeamB) ->
%%    事件通知
    gen_event:notify(Pid, {set_teams, TeamA, TeamB}).
%%添加分数
add_points(Pid, Team, N) ->
    gen_event:notify(Pid, {add_points, Team, N}).
%%下一回合
next_round(Pid) ->
    gen_event:notify(Pid, next_round).
 
%% Subscribes the pid ToPid to the event feed.
%% The specific event handler for the newsfeed is
%% returned in case someone wants to leave
%%join_feed(Pid, ToPid) ->
%%    HandlerId = {curling_feed, make_ref()},
%%    gen_event:add_sup_handler(Pid, HandlerId, [ToPid]),
%%    HandlerId.
%%
%%leave_feed(Pid, HandlerId) ->
%%    gen_event:delete_handler(Pid, HandlerId, leave_feed).
 
%% Returns the current game state.
game_info(Pid) ->
    gen_event:call(Pid, curling_accumulator, game_data).

事件处理器

-module(curling_scoreboard).
-behaviour(gen_event).
 
-export([init/1, handle_event/2, handle_call/2, handle_info/2, code_change/3,
         terminate/2]).
 
%%计分板
init([]) ->
    io:format("curling_scoreboard init ~n"),
    {ok, []}.
 
handle_event({set_teams, TeamA, TeamB}, State) ->
    io:format("curling_scoreboard set_teams ~n"),
    io:format("Scoreboard: Team ~s vs. Team ~s~n", [TeamA, TeamB]),
%%    curling_scoreboard_hw:set_teams(TeamA, TeamB),
    {ok, State};
handle_event({add_points, Team, N}, State) ->
    io:format("curling_scoreboard add_points ~n"),
    [io:format("Scoreboard: increased score of team ~s by 1~n", [Team]) || _ <- lists:seq(1,N)],
%%    [curling_scoreboard_hw:add_point(Team) || _ <- lists:seq(1,N)],
    {ok, State};
handle_event(next_round, State) ->
    io:format("curling_scoreboard next_round ~n"),
    io:format("Scoreboard: round over~n"),
%%    curling_scoreboard_hw:next_round(),
    {ok, State};
handle_event(_, State) ->
    {ok, State}.
 
handle_call(_, State) ->
    {ok, ok, State}.
 
handle_info(_, State) ->
    {ok, State}.
 
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.
 
terminate(_Reason, _State) ->
    ok.
-module(curling_accumulator).
-behaviour(gen_event).
 
-export([init/1, handle_event/2, handle_call/2, handle_info/2, code_change/3,
         terminate/2]).
 
-record(state, {teams=orddict:new(), round=0}).
 
%% 累加器
 
init([]) ->
    io:format("accumulator init ~n"),
    {ok, #state{}}.
 
handle_event({set_teams, TeamA, TeamB}, S=#state{teams=T}) ->
    io:format("accumulator set_teams ~n"),
    Teams = orddict:store(TeamA, 0, orddict:store(TeamB, 0, T)),
    {ok, S#state{teams=Teams}};
handle_event({add_points, Team, N}, S=#state{teams=T}) ->
    io:format("accumulator add_points ~n"),
    Teams = orddict:update_counter(Team, N, T),
    {ok, S#state{teams=Teams}};
handle_event(next_round, S=#state{}) ->
    io:format("accumulator next_round ~n"),
    {ok, S#state{round = S#state.round+1}};
handle_event(_Event, Pid) ->
    {ok, Pid}.
 
handle_call(game_data, S=#state{teams=T, round=R}) ->
    {ok, {orddict:to_list(T), {round, R}}, S};
handle_call(_, State) ->
    {ok, ok, State}.
 
handle_info(_, State) ->
    {ok, State}.
 
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.
 
terminate(_Reason, _State) ->
    ok.

up-1820eea78749dc393fb4e6e05932985f03a.png

三、具体过程

up-c83d2e8e47b8c6657c4b4dbc6100401b33a.png

直接从源码上看

Add handler 的过程

-spec add_handler(emgr_ref(), handler(), term()) -> term().

add_handler(M, Handler, Args) -> rpc(M, {add_handler, Handler, Args}).

调用gen:call

rpc(M, Cmd) -> 

    {ok, Reply} = gen:call(M, self(), Cmd, infinity),

    Reply.

事件管理器进程收到消息时调用 handle_msg(Msg, Parent, ServerName, MSL, Debug)

{From, Tag, {add_handler, Handler, Args}} ->

    {Hib, Reply, MSL1} = server_add_handler(Handler, Args, MSL),

    ?reply(Reply),

    loop(Parent, ServerName, MSL1, Debug, Hib);

新增一个handler记录里面记录了对应的mdule和ID,然后调用mdule:init(事件处理器初始化),返回后把新增的handler存进列表并作为进程主循环的函数参数

server_add_handler({Mod,Id}, Args, MSL) ->
    Handler = #handler{module = Mod,
             id = Id},
    server_add_handler(Mod, Handler, Args, MSL);
server_add_handler(Mod, Args, MSL) ->
    Handler = #handler{module = Mod},
    server_add_handler(Mod, Handler, Args, MSL).

server_add_handler(Mod, Handler, Args, MSL) ->
    case catch Mod:init(Args) of
        {ok, State} ->
       {false, ok, [Handler#handler{state = State}|MSL]};
        {ok, State, hibernate} ->
       {true, ok, [Handler#handler{state = State}|MSL]};
        Other ->
            {false, Other, MSL}
    end.

Notify

notify(M, Event) -> send(M, {notify, Event}).

向事件管理器进程发送消息

send({global, Name}, Cmd) ->

    catch global:send(Name, Cmd),

    ok;

send({via, Mod, Name}, Cmd) ->

    catch Mod:send(Name, Cmd),

    ok;

send(M, Cmd) ->

    M ! Cmd,

    ok.

事件管理器进程收到消息时调用 handle_msg(Msg, Parent, ServerName, MSL, Debug)

{notify, Event} ->

    {Hib,MSL1} = server_notify(Event, handle_event, MSL, ServerName),

    loop(Parent, ServerName, MSL1, Debug, Hib);

遍历handle列表,调用handle_event (Event, State)

server_notify(Event, Func, [Handler|T], SName) ->
    case server_update(Handler, Func, Event, SName) of
   {ok, Handler1} ->
       {Hib, NewHandlers} = server_notify(Event, Func, T, SName),
       {Hib, [Handler1|NewHandlers]};
   {hibernate, Handler1} ->
       {_Hib, NewHandlers} = server_notify(Event, Func, T, SName),
       {true, [Handler1|NewHandlers]};
   no ->
       server_notify(Event, Func, T, SName)
    end;
server_notify(_, _, [], _) ->
    {false, []}.

%% server_update(Handler, Func, Event, ServerName) -> Handler1 | no

server_update(Handler1, Func, Event, SName) ->
    Mod1 = Handler1#handler.module,
    State = Handler1#handler.state,
    case catch Mod1:Func(Event, State) of
   {ok, State1} ->
       {ok, Handler1#handler{state = State1}};
   {ok, State1, hibernate} ->
       {hibernate, Handler1#handler{state = State1}};
   {swap_handler, Args1, State1, Handler2, Args2} ->
       do_swap(Mod1, Handler1, Args1, State1, Handler2, Args2, SName);
   remove_handler ->
       do_terminate(Mod1, Handler1, remove_handler, State,
          remove, SName, normal),
       no;
   Other ->
       do_terminate(Mod1, Handler1, {error, Other}, State,
          Event, SName, crash),
       no
    end.

猜你喜欢

转载自blog.csdn.net/qq_25231683/article/details/120016935