esockd_connection_sup不是一个严格的supervisor。它只是一个gen_server。这是因为他的特殊性决定的。
start_link(Options, MFArgs, Logger) ->
gen_server:start_link(?MODULE, [Options, MFArgs, Logger], []).
supervisor中的必须有重启策略,如果没有在spec中填写默认就是one_for_one。这几种重启策略都会重启子进程。
但是对于sockt连接,断了就是断了,不应该重启的。因此不需要什么重启策略。那么supervisor怎么也得有个监督关系啊,
需要的是当子进程挂了的时候,supervisor要收到消息。
那他是怎么启动子进程(socket连接)的呢?
在esockd_connection_sup.erl中,Conn:start_link(MFArgs)函数调用 emqttd_client:start_link/2 来创建client进程。
esockd_connection_sup.erl:
start_connection(Sup, Mod, Sock, SockFun) ->
case call(Sup, {start_connection, Sock, SockFun}) of
{ok, Pid, Conn} ->
% transfer controlling from acceptor to connection
Mod:controlling_process(Sock, Pid),
Conn:go(Pid),
{ok, Pid};
{error, Error} ->
{error, Error}
end.
handle_call({start_connection, Sock, SockFun}, _From,
State = #state{conn_opts = ConnOpts, mfargs = MFArgs,
curr_clients = Count, access_rules = Rules}) ->
case inet:peername(Sock) of
{ok, {Addr, _Port}} ->
case allowed(Addr, Rules) of
true ->
Conn = esockd_connection:new(Sock, SockFun, ConnOpts),
case catch Conn:start_link(MFArgs) of
{ok, Pid} when is_pid(Pid) ->
...
emqttd_client:start_link/2 调用 proc_lib:spawn_link/3 来启动进程:
emqttd_client.erl:
start_link(Conn, Env) ->
{ok, proc_lib:spawn_link(?MODULE, init, [[Conn, Env]])}.
为什么这里要使用proc_link:spwan_link/3来启动连接进程呢?因为这个函数最终是调用erlang:spawn_link来启动,并自动创建link。
该函数和erlang:start_link的方式区别是spawn_link属于异步启动进程。一调用就会返回子进程ID。
他的用处在emqttd_client:init中看到:
init([Conn0, Env]) ->
{ok, Conn} = Conn0:wait(),
case Conn:peername() of
{ok, Peername} -> do_init(Conn, Env, Peername);
{error, enotconn} -> Conn:fast_close(),
exit(normal);
{error, Reason} -> Conn:fast_close(),
exit({shutdown, Reason})
end.
这里的 Conn0:wait():
esockd_connection.erl:
wait(Conn = ?CONN_MOD) ->
receive {go, Conn} -> upgrade(Conn) end.
使用 receive 来接受消息{go, Conn}。如果emqttd_client:start_link中不使用spawn_link来启动进程,那么在 init 中就会卡死。
这样在 esockd_connection_sup:start_connection(Sup, Mod, Sock, SockFun) 中,Conn:go()就不会被执行。因此出现wait()一直
等待go()发出消息。
如果使用spawn_link就会直接返回,init中执行wait,go被执行后发出消息由wait收到,然后才执行do_init(Conn, Env, Peername)
函数。
另外,esockd_connection_sup 中和子进程link之后,相互都会收到对方 exit 的消息,这样可能 esockd_connection_sup 可能会因为
子进程挂掉而挂掉,为了避免这种情况,esockd_connection_sup 启动的时候在init中设置 process_flag(trap_exit, true), 这样
可以将子进程发送的 exit 消息转化为消息{'EXIT', Pid, Reason},从而避免 esockd_connection_sup 被牵连而挂掉。
handle_info({'EXIT', Pid, Reason}, State = #state{curr_clients = Count, logger = Logger}) ->
...
总结一下:只要理解了spawn_link的异步方式就可以理解wait和go了。