初学者的 Erlang/OTP 行为

Posted

技术标签:

【中文标题】初学者的 Erlang/OTP 行为【英文标题】:Erlang/OTP behaviors for beginner 【发布时间】:2011-08-13 01:05:08 【问题描述】:

正如我从“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, #stateport = 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#staterequest_count = RequestCount + 1;
handle_info(timeout, #statelsock = 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).

【问题讨论】:

【参考方案1】:

gen_server:start_link 调用 init。

【讨论】:

【参考方案2】:

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

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

它使编译器能够给出特定于 ex: gen_server 行为的错误消息

问:“回调函数”在上下文中的实际含义 Elang/OTP?

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

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

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

是的...也许...不:)

问:书上说库的相关回调函数 以下代码中的函数 'gen_server:start_link/4' 是 '模块:init/1'。

gen_server:start_link 按照 w55 的回答自行调用 init 函数....(对不起,名字很大)。


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

【讨论】:

阿伦,这正是我一直在寻找的。​​span> 【参考方案3】:

查看 erlang lib 目录中的 gen_server 模块的源代码。源码里解释的很好,cmets很详细。

【讨论】:

【参考方案4】:

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)
  ...
...

【讨论】:

我实际上不认为这个答案很有帮助。其中大部分都带有 RTFM 的味道,这在暗示它尚未被阅读时是冒昧的,而且无关紧要。对概念的替代且可能更简单的解释是学习的基础。 *** 上的人们应该努力提供自己的问题答案而不是指针,除非特别请求指针。 服务器的代码可以改写成一个通用的部分 server.erl: .. 你能解释一下这行吗?我以为整个代码都是用于服务器的【参考方案5】:

我不会像其他答案那样尝试解决您的具体问题,而是尝试简单地解释行为背后的基础知识,并让您在了解这些基础知识的基础上回答自己的问题。

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

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

行为将消息处理委托给回调模块,或行为实现作为“Erlang and 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,您会发现一个很像这样的循环,但由于处理系统消息、超时、跟踪、异常等。标准行为也在一个单独的进程中启动它们的循环,因此它们还包含用于启动循环进程并将消息传递给它的代码。

【讨论】:

谢谢史蒂夫,我会在进一步阅读本书时参考您的答案。 亲爱的主,我终于明白行为是如何运作的了。谢谢史蒂夫!

以上是关于初学者的 Erlang/OTP 行为的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Erlang/OTP 中将主管的孩子 pid 共享给另一个孩子

Erlang/OTP 升级丢失现有的 RabbitMQ 消息

您如何设计基于 Erlang/OTP 的分布式容错多核系统的架构?

Erlang/OTP 生产应用部署简介

您如何设计基于Erlang / OTP的分布式容错多核系统的架构?

Erlang/OTP 18.0 正式版发布