Erlang 监督一个 UDP 侦听器
Erlang supervising an UDP listener
我正在学习 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 模块。之后写一个主管模块。我的主管长这样:
-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 正在打开另一个进程,但是它正在与调用进程进行 link 。据我了解,我的主管将在这里监控调用过程。那么,如果我的 udplistener 出现故障,我的主管会如何表现?如果它没有按我预期的方式工作(我希望它会重新启动我的服务器),最好的方法是什么?
创建套接字并将其传递给工作人员的主管初始化回调实现存在问题。
除了版本升级的例外情况,你的 supervisor init 回调实际上只会被调用一次,所以在 supervisor init 中创建套接字并将其传递给 worker 意味着套接字永远不会有任何机制来重新开放。但是,如果您在 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,它会重新打开套接字并继续接收...
同样在你的 workers init 中,打开套接字后,注意套接字实际上是一个端口,你可能应该通过调用 erlang:link/1 来 link 到它,像这样:
{ok, Socket} = gen_udp:open(9000,[binary,{active,true}]),
erlang:link(Socket),
我不确定 gen_udp 是否会为您执行此操作,但我看不到文档中是这样说的,安全总比抱歉好。现在这意味着如果端口死了但你的工作人员没有死, link 将导致你的工作人员也死掉,主管将重启你的工作人员,这将重启你的端口。如果你想避免你的工作人员死在你可以捕获出口的地方,但是简单地让你的工作人员死掉更适合 Erlangs fail early principal IMO,这意味着如果你的套接字打开不断失败,主管将重新启动强度并做一些不同的事情,而不是让你的工人盲目地永远尝试重新打开插座。所以现在就这样做,如果你有理由,以后再改变你的策略。
我正在学习 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 模块。之后写一个主管模块。我的主管长这样:
-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 正在打开另一个进程,但是它正在与调用进程进行 link 。据我了解,我的主管将在这里监控调用过程。那么,如果我的 udplistener 出现故障,我的主管会如何表现?如果它没有按我预期的方式工作(我希望它会重新启动我的服务器),最好的方法是什么?
创建套接字并将其传递给工作人员的主管初始化回调实现存在问题。
除了版本升级的例外情况,你的 supervisor init 回调实际上只会被调用一次,所以在 supervisor init 中创建套接字并将其传递给 worker 意味着套接字永远不会有任何机制来重新开放。但是,如果您在 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,它会重新打开套接字并继续接收...
同样在你的 workers init 中,打开套接字后,注意套接字实际上是一个端口,你可能应该通过调用 erlang:link/1 来 link 到它,像这样:
{ok, Socket} = gen_udp:open(9000,[binary,{active,true}]),
erlang:link(Socket),
我不确定 gen_udp 是否会为您执行此操作,但我看不到文档中是这样说的,安全总比抱歉好。现在这意味着如果端口死了但你的工作人员没有死, link 将导致你的工作人员也死掉,主管将重启你的工作人员,这将重启你的端口。如果你想避免你的工作人员死在你可以捕获出口的地方,但是简单地让你的工作人员死掉更适合 Erlangs fail early principal IMO,这意味着如果你的套接字打开不断失败,主管将重新启动强度并做一些不同的事情,而不是让你的工人盲目地永远尝试重新打开插座。所以现在就这样做,如果你有理由,以后再改变你的策略。