初学者的Erlang / OTP行为

时间:2011-04-26 13:06:52

标签: callback erlang otp

正如我从“Erlang and OTP in action”一书中所理解的那样,行为一词指的是:

  • 行为界面,这是一组功能;
  • 行为实现,即特定于应用程序的代码(回调模块);
  • 行为容器,这是一个过程。

问题:

Erlang / OTP初学者应该了解哪些行为?是否有可能简单地描述和理解OTP行为的概念?

“回调函数”在Elang / OTP的上下文中实际意味着什么?

我们可以考虑在Java中覆盖方法的行为实现中的回调吗?

本书说下面代码中库函数'gen_server:start_link / 4'的关联回调函数是'Module:init / 1'。

这是否意味着使用init / 1我们调用gen_server:start_link / 4库函数?或者这意味着什么?

-module(tr_server).

-behaviour(gen_server).

-include_lib("eunit/include/eunit.hrl").

%% API
-export([
         start_link/1,
         start_link/0,
         get_count/0,
         stop/0
         ]).

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

-define(SERVER, ?MODULE).
-define(DEFAULT_PORT, 1055).

-record(state, {port, lsock, request_count = 0}).


%%%===================================================================
%%% API
%%%===================================================================


%%--------------------------------------------------------------------
%% @doc Starts the server.
%%
%% @spec start_link(Port::integer()) -> {ok, Pid}
%% where
%%  Pid = pid()
%% @end
%%--------------------------------------------------------------------
start_link(Port) ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [Port], []).

%% @spec start_link() -> {ok, Pid}
%% @doc Calls `start_link(Port)' using the default port.
s    tart_link() ->
    start_link(?DEFAULT_PORT).

%%--------------------------------------------------------------------
%% @doc Fetches the number of requests made to this server.
%% @spec get_count() -> {ok, Count}
%% where
%%  Count = integer()
%% @end
%%--------------------------------------------------------------------
get_count() ->
    gen_server:call(?SERVER, get_count).

%%--------------------------------------------------------------------
%% @doc Stops the server.
%% @spec stop() -> ok
%% @end
%%--------------------------------------------------------------------
stop() ->
    gen_server:cast(?SERVER, stop).


%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

init([Port]) ->
    {ok, LSock} = gen_tcp:listen(Port, [{active, true}]),
    {ok, #state{port = Port, lsock = LSock}, 0}.

handle_call(get_count, _From, State) ->
    {reply, {ok, State#state.request_count}, State}.

handle_cast(stop, State) ->
    {stop, normal, State}.

handle_info({tcp, Socket, RawData}, State) ->
    do_rpc(Socket, RawData),
    RequestCount = State#state.request_count,
    {noreply, State#state{request_count = RequestCount + 1}};
handle_info(timeout, #state{lsock = LSock} = State) ->
    {ok, _Sock} = gen_tcp:accept(LSock),
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

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

%%%===================================================================
%%% Internal functions
%%%===================================================================

do_rpc(Socket, RawData) ->
    try
        {M, F, A} = split_out_mfa(RawData),
        Result = apply(M, F, A),
        gen_tcp:send(Socket, io_lib:fwrite("~p~n", [Result]))
    catch
        _Class:Err ->
            gen_tcp:send(Socket, io_lib:fwrite("~p~n", [Err]))
    end.

split_out_mfa(RawData) ->
    MFA = re:replace(RawData, "\r\n$", "", [{return, list}]),
    {match, [M, F, A]} =
        re:run(MFA,
               "(.*):(.*)\s*\\((.*)\s*\\)\s*.\s*$",
                   [{capture, [1,2,3], list}, ungreedy]),
    {list_to_atom(M), list_to_atom(F), args_to_terms(A)}.

args_to_terms(RawArgs) ->
    {ok, Toks, _Line} = erl_scan:string("[" ++ RawArgs ++ "]. ", 1),
    {ok, Args} = erl_parse:parse_term(Toks),
    Args.


%% test

start_test() ->
    {ok, _} = tr_server:start_link(1055).

5 个答案:

答案 0 :(得分:21)

我会尝试用简单的术语解释行为背后的基本知识,让您在理解这些基础知识的基础上回答自己的问题,而不是试图解决您的具体问题。

行为基本上是一个消息处理框架,其中“框架”是指可以由最终用户完成和定制的问题的部分解决方案的经典定义。 OTP行为基本上提供:

  • 消息循环
  • 与代码升级,跟踪,系统消息等的底层OTP支持集成。

行为将消息处理委托给回调模块,或将行为实现委托给“Erlang和OTP In Action”调用它们。在调用其init/1函数时,回调模块通常会为消息循环创建状态以保持其状态。然后,行为循环将此状态传递给回调模块消息处理函数的每个后续调用,并且这些调用中的每一个都可以返回修改后的状态。回调函数还返回指示行为消息循环的指令。

这是行为核心的消息循环的极大简化版本:

loop(Callbacks, State) ->
  {Next, NState} =
 receive
                     M1 ->

                       Callbacks:handle_m1(M1,State);
                     M2 ->
                       Callbacks:handle_m2(M2,State);
                     Other ->
                       Callbacks:handle_other(Other,State)
                   end,
  case Next of

    stop -> ok;
    _ -> loop(Callbacks, NState)
  end.

这个尾递归循环有Callbacks模块和State变量作为参数。在首次调用此循环之前,您已经告诉行为您的回调模块是什么,然后基本OTP行为支持代码已调用您的init/1回调函数来获取State的初始值。

我们的示例行为循环接收形式为M1M2的消息以及任何其他消息,其详细信息在此处无关紧要,并且对于每条消息,都会调用其中的不同回调函数。 Callbacks模块。在此示例中,handle_m1handle_m2回调函数分别处理消息M1M2,而回调handle_other处理所有其他类型的消息。请注意,State将传递给每个回调函数。每个函数都要返回一个元组,第一个元素告诉循环下一步该做什么,第二个元素包含循环的可能新状态 - 与State相同的值或新的不同值 - 循环存储在变量NState中。在此示例中,如果Next是原子stop,则循环停止,但如果它是其他任何东西,则循环以递归方式调用自身,将新状态NState传递给下一次迭代。因为它是尾递归的,所以循环不会炸掉堆栈。

如果你仔细研究标准OTP行为的来源,例如gen_servergen_fsm,你会发现一个类似于此的循环,但由于处理系统消息,它们要复杂得多,超时,跟踪,异常等。标准行为也在一个单独的进程中启动它们的循环,因此它们还包含用于启动循环过程并将消息传递给它的代码。

答案 1 :(得分:19)

  

问: Erlang / OTP初学者应该了解哪些行为?是吗   可以描述和理解一个OTP行为的概念   简而言之?

行为通常用在代码中,以便编译器可以根据其行为生成更直观的错误消息,即application / supervisor / gen_server / gen_event / gen_fsm。

它使编译器能够提供特定于ex:gen_server

行为的错误消息
  

问:'回调函数'在上下文中实际意味着什么   饿狼/ OTP?

可以说回调函数取自GUI编程(至少类似)。每当事件发生在前。鼠标单击,有一个单独的功能,可以处理鼠标点击。

因此,无论何时,例如。从另一个模块调用gen_server的导出函数,该函数可以有一个具有不同模式的回调函数(handle_call / handle_cast)。

  

问:我们可以将行为实现中的回调视为方法吗?   在Java中被覆盖?

是啊......也许......不:)

  

问:这本书说明了图书馆的相关回调函数   以下代码中的函数'gen_server:start_link / 4'是   '模块:初始化/ 1'。

gen_server:start_link自己调用init函数,由w55回答....(对不起,这是一个很大的名字)。


希望我已经回答了所有问题:)

答案 2 :(得分:12)

  

Erlang / OTP初学者应该了解哪些行为?

可能写的是here

  

简而言之,是否有可能描述和理解OTP行为的概念?

从文档中读取:“行为是这些常见模式的形式化。我们的想法是在通用部分(行为模块)和特定部分(回调模块)中划分流程的代码。”

  

“回调函数”在Elang / OTP的上下文中实际意味着什么?

请查看上面的链接,其中提供了回调函数的示例。

  

我们可以考虑在Java中覆盖方法的行为实现中的回调吗?

在Java术语中,行为可能是Java接口,而回调则是接口中定义的方法之一的实现。

  

本书说,以下代码中库函数'gen_server:start_link / 4'的关联回调函数是'Module:init / 1'。   这是否意味着使用init / 1我们调用gen_server:start_link / 4库函数?或者这意味着什么?

这意味着,每次调用gen_server:start_link / 4时,都会调用函数Module:init / 1,其中Module是传递给start_link函数的第二个参数,其中你提供的参数是第四个论点。换句话说,这就是start_link / 4幕后发生的事情:

...
start_link(Name, Module, Args, Opts) ->
  ...
  Module:init(Args)
  ...
...

答案 3 :(得分:3)

查看erlang lib目录中gen_server模块的源代码。它在源代码中得到了很好的解释,评论非常精细。

答案 4 :(得分:0)

gen_server:start_link调用init。