我是Erlang初学者,并试图实现我的第一个Erlang应用程序。它是一个网络监视工具,应该对指定的主机执行ping请求。实际上发送ICMP不是一个目标,我对应用程序结构更感兴趣。目前我有monitor_app,monitor_sup(root sup),pinger_sup和pinger(worker)。这是pinger_sup:
-module(pinger_sup).
-behaviour(supervisor).
-export([start_link/0, start_child/1, stop_child/1]).
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
start_child(Ip) ->
{ok, Pid} = supervisor:start_child(?MODULE, Ip),
put(Pid, Ip),
{ok, Pid}.
stop_child(Ip) ->
Children = get_keys(Ip),
lists:foreach(fun(Pid) ->
pinger:stop(Pid)
end,
Children).
init(_Args) ->
Pinger = {pinger, {pinger, start_link, []},
transient, 2000, worker, [pinger]},
Children = [Pinger],
RestartStrategy = {simple_one_for_one, 4, 3600},
{ok, {RestartStrategy, Children}}.
并且自己:
-module(pinger).
-behaviour(gen_server).
-export([start_link/1, stop/1, stop_ping/1, ping/1, ping/2]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(PING_INTERVAL, 5000).
start_link(Ip) ->
gen_server:start_link(?MODULE, Ip, []).
stop(Pid) ->
gen_server:cast(Pid, stop).
stop_ping(Ip) ->
pinger_sup:stop_child(Ip).
ping(Ip) ->
pinger_sup:start_child(Ip).
ping(_Ip, 0) ->
ok;
ping(Ip, NProc) ->
ping(Ip),
ping(Ip, NProc - 1).
init(Ip) ->
erlang:send_after(1000, self(), do_ping),
{ok, Ip}.
handle_call(_Request, _From, State) ->
{noreply, State}.
handle_cast(stop, State) ->
io:format("~p is stopping~n", [State]),
{stop, normal, State}.
handle_info(do_ping, State) ->
io:format("pinging ~p~n", [State]),
erlang:send_after(?PING_INTERVAL, self(), do_ping),
{noreply, State};
handle_info(Info, State) ->
io:format("Unknown message: ~p~n", [Info]),
{noreply, State}.
terminate(_Reason, State) ->
io:format("~p was terminated~n", [State]),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
它缺乏评论,但我认为任何Erlang开发人员都非常直接和熟悉。关于此代码我几乎没有问题:
1)* erlang:send_after *在init中是一个很好的解决方案吗?如果我需要流程开始立即完成工作,那么最佳实践是什么?它是在没有从外部触发其功能的情况下产生的?
2)目前我使用 pinger:ping(“127.0.0.1”)。命令启动ping。 pinger模块请求pinger_sup启动子进程。该过程开始后我想停止它。这样做的最佳方式是什么?我应该通过sup终止它还是我应该发送停止命令?我应该如何存储过程PID?目前我使用进程字典但在实现之后我意识到字典实际上不属于sup(它是shell字典或任何其他stop_child调用者)。如果我使用ETS并且sup被终止,那么这些死进程PID会永远停留在ETS中导致内存泄漏吗?
我感谢任何答案和代码评论。
谢谢!
答案 0 :(得分:7)
1) erlang:send_after 在init中是一个很好的解决方案吗?
没有
如果我需要流程开始立即完成工作,那么最佳实践是什么?它是在没有从外部触发其功能的情况下产生的?
见here。将超时设为0,然后执行:
handle_info(timeout, State) ->
whatever_you_wish_to_do_as soon_as_server_starts,
{noreply, State}.
零超时意味着服务器在完成初始化之后会立即发送“超时”信息,然后再处理任何其他调用/强制转换。
另请参阅timer module。而不是在handle_info(do_ping,State)中重新调用send_after,只需启动计时器并告诉他每次发送“do_ping”?PING_INTERVAL。
2)目前我使用pinger:ping(“127.0.0.1”)启动ping。命令。 pinger模块请求pinger_sup启动子进程。该过程开始后我想停止它。这样做的最佳方式是什么?我应该通过sup终止它还是我应该发送停止命令?
你应该给他发一个停止命令。为什么使用supervisor来杀死gen_server,如果gen_server可以单独执行呢? : - )
我应该如何存储过程PID?目前我使用进程字典但在实现之后我意识到字典实际上不属于sup(它是shell字典或任何其他stop_child调用者)。如果我使用ETS并且sup被终止,那么这些死进程PID会永远停留在ETS中导致内存泄漏吗?
所有者进程终止后,ETS表将被销毁。所以,那里没有内存泄漏。
但是,以你的方式存储PID不是“erlang方式”。相反,我建议制作一个主管树。而不是将所有pinger工作者放在pinger_sup下然后记住哪个工作人员ping哪个IP,我建议pinger_sup启动一个处理给定IP的主管。然后这位主管启动了所需数量的工人。
现在,当你想停止ping一些IP时,你只需要杀死该IP的主管,他就会自动杀死他的孩子。
你怎么知道哪个主管处理哪个IP?好吧,把IP放在主管的名字中:-)当产生处理IP的主管时,做这样的事情:
-module(pinger_ip_sup).
start_link(Ip) ->
supervisor:start_link({global, {?MODULE, Ip}}, ?MODULE, []).
然后,当你想停止ping Ip时,你只需要用名称{global,{pinger_ip_sup,Ip}}杀死主管,他就会杀了他的孩子: - )
关于评论的修改:
如果您希望处理产生超时的错误,您可以保留一个状态变量,该变量将告诉您它是由init产生的超时还是由错误产生的超时。例如:
-record(pinger_state, {ip, initialized}).
init(Ip) ->
State = #pinger_state{ip = Ip, initialized = false}.
{ok, State, 0}.
handle_info(timeout, State#pinger_state{initialized = false}) ->
whatever_you_wish_to_do_as soon_as_server_starts,
New_state = State#pinger_state{initialized = true}.
{noreply, New_state}.
handle_info(timeout, State#pinger_state{initialized = true}) ->
error_handling,
{noreply, State}.
这样您就可以使用此机制并处理超时错误。但是,真正的问题是:你是否期望超时错误?
至于计时器:是的,计时器有一些开销,以防你计划进行DDOS攻击或类似的事情:-D如果你不打算像疯狂一样创建和取消计时器,那么使用计时器模块会更优雅。这是一个你必须做出的设计选择:你想要一个干净优雅的代码,或者代码,当其他一切都破坏时它们可以代替。我怀疑你需要后者。但是,你最清楚。
答案 1 :(得分:1)
mybe你需要什么“存储主管儿童PID”是 二郎:寄存器(原子,PID)