【问题标题】:Erlang supervising an UDP listenerErlang 监督 UDP 监听器
【发布时间】:2016-02-12 00:20:30
【问题描述】:

我正在学习 Erlang。我想制作一个由主管监督的 UDP 侦听器。因此,如果侦听器进程出现故障,主管将重新启动该进程。最初我只是做了一个简单的 UDP 监听器,它按预期工作。

startudplistener() ->
    {ok, Socket} = gen_udp:open(9000,[binary,{active,false}]),
    Pid = spawn(pdmanager,udplistener,[Socket]),
    {ok, Pid}.

udplistener(Socket) ->
    {ok,Packet} = gen_udp:recv(Socket,0),
    spawn(pdmanager,handleudp,[Packet]),
    udplistener(Socket).

handleudp(Packet) ->
    {_,_, Msg} = Packet,
    io:format("I have got message : ~s ~n",[Msg]),
    {handeling, Packet}.

所以,我想做的是监控 udplistener 进程。为此,我首先将模块修改为 gen_server 模块。之后写一个supervisor模块。我的主管长这样:

-module(pdmanager_sup).
-behaviour(supervisor).

-export([start_link/1]).
-export([init/1]).

start_link(Port) ->
supervisor:start_link({local,?MODULE}, ?MODULE, Port).

init(Port) ->
    {ok, Socket} = gen_udp:open(Port, [binary, {active, false}]),
    {ok, {{one_for_one, 5, 60},
        [{listener,
            {pdmanager, start_link, [Socket]},
            permanent, 1000, worker, [pdmanager]}
        ]}}.

所以我想要这样做的是,打开一个新的 udp 套接字并将其传递给我的服务器,服务器将继续侦听套接字,而主管将监视它。所以我想出了以下代码。

start_link(Socket) ->
    gen_server:start_link({local, pdmanager}, pdmanager, Socket, []).

init(Socket) ->
    io:format("UDP Server starting ~n",[]),
    spawn_link(pdmanager,udplistener,[Socket]),
    {ok, #state{socket=Socket}}.

我对我在 init 函数中添加的 spawn_link 有点困惑。 spawn_link 正在打开另一个进程,但是它正在与调用进程建立链接。据我了解,我的主管将在这里监控呼叫过程。那么,如果我的 udplistener 出现故障,我的主管会如何表现?如果它不能按我预期的方式工作(我希望它会重新启动我的服务器),那么最好的方法是什么?

【问题讨论】:

    标签: erlang


    【解决方案1】:

    创建套接字并将其传递给工作人员的主管初始化回调实现是一个问题。

    除了发布升级的例外情况,您的主管初始化回调实际上只会被调用一次,因此在主管初始化中创建套接字并将其传递给工作人员意味着永远不会有任何机制可以重新打开套接字.但是,如果您在 gen_server worker 的 init 回调中打开套接字,那么当 worker 重新启动时,套接字的任何问题都会得到解决。

    下一个问题是您在非活动模式下使用套接字。处于非活动模式的套接字实际上只对相对裸露的进程有用和有益(即不是 gen_server 说),因为当您调用 gen_udp:recv 它可以在等待数据到达时阻塞......这意味着 gen_server 被阻塞并且无法提供任何应有的服务。所以现在你要沿着 OTP 路径,使用主管和 gen_servers,你应该切换到在活动模式下使用套接字,这意味着 UDP 数据包将作为消息发送到你的 gen_server。然后你可以通过你的 handle_info 回调实现来接收:

    handle_info({udp, Socket, IP, InPortNo, Packet}, #state{socket=Socket}) ->
        io:format("whooopie, got a packet ~p~n", [Packet]),
    

    如果你的 gen_server worker 死掉了,那没关系,端口也会消失,主管将启动一个新的 worker,它将重新打开套接字并继续接收......

    同样在你的worker初始化中,打开socket后,注意socket实际上是一个端口,你应该通过调用erlang:link/1链接到它,像这样:

    {ok, Socket} = gen_udp:open(9000,[binary,{active,true}]),
    erlang:link(Socket),
    

    我不确定 gen_udp 是否会为您执行此操作,但我看不到文档中是这样说的,而且安全总比抱歉好。现在这意味着如果端口死了,但你的工人没有,链接将导致你的工人也死,主管将重新启动你的工人,这将重新启动你的端口。如果你想避免你的工人在你可以的地方死去,你可以用陷阱来代替,但只是让你的工人死去更适合 Erlangs failed early principal IMO,这意味着如果你的套接字打开不断失败,主管将重新启动强度并做一些不同的事情,而不是让你的工人盲目地一直试图重新打开套接字。所以现在就这样做,以后如果有理由改变你的策略。

    【讨论】:

    • 您好,非常感谢您的回复。万分感激。我没有得到handle_info 的东西。我了解到handle_info 处理直接使用(!)发送到进程的异步调用。那么,在活动模式下,udp 数据包会像这样发送到我的服务器进程吗?不需要做 gen_udp:recv/2 吗?另外,您提到“gen_udp:recv 它可以在等待数据到达时阻塞”但是,我产生了一个新进程,我在其中调用了 gen_udp:recv() 它是否仍会阻塞?
    • 另外,关于你提到的套接字,“这意味着如果端口死了但你的工人没有死,链接会导致你的工人也死”;在我的代码中,我使用了 spawn_link。我了解到 spawn_link 启动了一个新进程并将其与父进程链接。它的工作方式是否像孩子死了会导致父母也死一样?
    • 是的,当您打开套接字时,您可以想象它就像一个进程,代表您接收 UDP 数据并将其发送给您。端口不是进程,但有一些相似之处。是的,您可以生成 recv(我没有意识到这是您的意图),它仍然会阻塞,但会阻塞生成的进程,但是您必须将收到的数据发送回父 gen_server 并发送一条消息一些有用的东西(但没有意义,因为活动模式会为你做这件事),或者在生成的过程中做一些事情(但这并不真正适合 OTP)。
    • 在您的主管中,您指定{pdmanager, start_link, [Socket]},因此主管将您的 gen_server 启动为 pdmanager:start_link(Socket),它调用 gen_server:start_link,生成 gen_server 并链接到它(可能调用 spawn_link) - - 因此主管连接到您的 gen_server 并可以对其进行监督(因为主管捕获死亡并采取重新启动您的 gen_server 的操作,而不是默认情况下自行死亡)。通过链接到您的端口,您可以将监督链从 gen_server 扩展到端口/套接字,除了在这种情况下死亡就足够了。
    猜你喜欢
    • 2011-08-07
    • 2012-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-12
    • 1970-01-01
    • 2023-03-02
    • 2015-05-16
    相关资源
    最近更新 更多