监督UDP侦听器的Erlang

监督UDP侦听器的Erlang,erlang,Erlang,我正在学习二郎。我想做一个UDP监听器,由监听器监督。因此,如果侦听器进程停止,主管将重新启动该进程。最初,我只是制作了一个简单的UDP侦听器,它可以正常工作 startudplistener() -> {ok, Socket} = gen_udp:open(9000,[binary,{active,false}]), Pid = spawn(pdmanager,udplistener,[Socket]), {ok, Pid}. udplistener(Socke

我正在学习二郎。我想做一个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}.
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}}.
所以,我想做的是监视udplistener过程。首先,我将我的模块修改为gen_server one。之后编写一个管理器模块。我的上司是这样的:

-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]}
        ]}}.
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}}.
{ok, Socket} = gen_udp:open(9000,[binary,{active,true}]),
erlang:link(Socket),
所以我想做的是,打开一个新的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_链接有点困惑。spawn_link正在打开另一个进程,但是它正在与调用进程建立链接。据我所知,我的主管将在这里监控呼叫过程。那么,如果我的udplistener倒下,我的主管会怎么做?如果它不能按我期望的方式工作(我期望它会重新启动我的服务器),那么最好的方法是什么?

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

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}}.
除了发布升级的例外情况外,实际上只会调用一次supervisor init回调,因此在supervisor init中创建套接字并将其传递给工作进程意味着没有任何机制可以重新打开套接字。但是,如果您在gen_server worker的init回调中打开套接字,那么当worker重新启动时,套接字的任何问题都将得到解决

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}}.
下一个问题是在非活动模式下使用套接字。非活动模式下的套接字实际上只对相对空闲的进程有用(例如,不是gen_服务器),因为当您调用gen_udp:recv时,它会在等待数据到达时阻塞。。。这意味着gen_服务器被阻塞,无法提供任何它应该提供的服务。因此,现在您要沿着OTP路径,使用管理器和gen_服务器,您应该切换到在活动模式下使用套接字,这意味着UDP数据包将作为消息发送到gen_服务器。然后,您可以通过handle_info回调实现接收:

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}}.
handle_info({udp, Socket, IP, InPortNo, Packet}, #state{socket=Socket}) ->
    io:format("whooopie, got a packet ~p~n", [Packet]),
如果您的gen_服务器工作线程死亡,这很好,端口也将关闭,并且主管将启动一个新的工作线程,该工作线程将重新打开套接字并再次继续接收

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}}.
同样在workers init中,打开套接字后,请注意套接字实际上是一个端口,您可能应该通过调用erlang:link/1链接到它,如下所示:

-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]}
        ]}}.
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}}.
{ok, Socket} = gen_udp:open(9000,[binary,{active,true}]),
erlang:link(Socket),

我不确定gen_udp是否会为您这样做,但我看不出文档中有这样的说法,安全总比抱歉好。这现在意味着,如果端口死亡,而不是您的工作进程,则链接将导致您的工作进程也死亡,并且主管将重新启动您的工作进程,从而重新启动您的端口。如果您想避免您的工作人员在可能的情况下死亡,您可以改为陷阱出口,但简单地让您的工作人员死亡更适合Erlangs fail early,这意味着如果您的套接字打开不断失败,监管者将达到重启强度,并做一些不同的事情,而不是让你的工人无意识地继续尝试重新打开插座。所以,现在就这样做,如果你有理由的话,以后再改变你的策略。

嗨,非常感谢你的回复。非常感谢。我不知道怎么处理这件事。我学习了handle_info处理直接使用(!)发送到进程的异步调用。那么,在活动模式下,udp数据包会像这样发送到我的服务器进程吗?不需要执行gen_udp:recv/2?另外,您提到“gen_udp:recv它可以在等待数据到达时阻塞”但是,我产生了一个新的进程,我调用了gen_udp:recv(),它是否仍然阻塞?另外,关于您提到的套接字,“这现在意味着如果端口死亡,而不是您的工作者,则链接将导致您的工作者也死亡”;在我的代码中,我使用了spawn_链接。我了解到spawn_link启动一个新进程并将其与父进程链接。它的工作方式是否与孩子死亡导致父母死亡的方式相同?是的,当您打开套接字时,您可以想象它就像一个进程,代表您接收UDP数据并将其发送给您。端口不是进程,但有一些相似之处。是的,您可以生成recv(我没有意识到这是您的意图),它仍然会阻止,但会阻止生成的进程,但您必须将接收到的数据发送回父gen_服务器,并发送一条消息,以便对其执行一些有用的操作(但没有意义,因为活动模式将为您执行此操作),或者在生成的过程中执行一些操作(但这并不适合OTP)。在您的主管中指定
{pdmanager,start\u link,[Socket]}
,因此主管将以pdmanager:start\u link(Socket)的形式启动gen\u服务器,它调用gen\u服务器:start\u link,生成gen\u服务器并链接到它(可能称为spawn\u link)--因此,主管将链接到您的gen_服务器并可以对其进行监督(因为主管会捕获死机并采取重新启动gen_服务器的操作,而不是默认情况下自行死机)。通过链接到您的端口,您可以将监控链从gen_服务器扩展到端口/套接字,但在这种情况下,只需死掉就足够了。
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}}.