现在Erlang网络库基本上都是使用ranch,今天我们就看看源码怎么实现的
首先,在我们的印象里,应该只要listen和accept,然后使用socket收发信息就可以了,但是为了让程序更加健壮,我们必须合理的安排哪些是supervisor,哪些是worker,废话不多说show me code
1、启动ranch,看ranch_app:start/2
start(_, _) ->
_ = consider_profiling(),
ranch_sup:start_link().
看ranch_sup:start_link().
-module(ranch_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
-spec start_link() -> {ok, pid()}.
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
Intensity = case application:get_env(ranch_sup_intensity) of
{ok, Value1} -> Value1;
undefined -> 1
end,
Period = case application:get_env(ranch_sup_period) of
{ok, Value2} -> Value2;
undefined -> 5
end,
ranch_server = ets:new(ranch_server, [
ordered_set, public, named_table]),
Procs = [
{ranch_server, {ranch_server, start_link, []},
permanent, 5000, worker, [ranch_server]}
],
{ok, {{one_for_one, Intensity, Period}, Procs}}.
读取配置,启动 ranch_server(主要是设置,获取服务器的一些配置和资源情况,使用ETS)
到这里,ranch就启动完毕了,启动了一个ranch_sup,下面挂了个ranch_server
2、再看如何启动服务端的listen和accept
看ranch模块
-spec start_listener(ref(), module(), opts(), module(), any())
-> supervisor:startchild_ret().
start_listener(Ref, Transport, TransOpts0, Protocol, ProtoOpts)
when is_atom(Transport), is_atom(Protocol) ->
TransOpts = normalize_opts(TransOpts0),
_ = code:ensure_loaded(Transport),
case erlang:function_exported(Transport, name, 0) of
false ->
{error, badarg};
true ->
Res = supervisor:start_child(ranch_sup, child_spec(Ref,
Transport, TransOpts, Protocol, ProtoOpts)),
Socket = maps:get(socket, TransOpts, undefined),
case Res of
{ok, Pid} when Socket =/= undefined ->
%% Give ownership of the socket to ranch_acceptors_sup
%% to make sure the socket stays open as long as the
%% listener is alive. If the socket closes however there
%% will be no way to recover because we don't know how
%% to open it again.
Children = supervisor:which_children(Pid),
{_, AcceptorsSup, _, _}
= lists:keyfind(ranch_acceptors_sup, 1, Children),
Transport:controlling_process(Socket, AcceptorsSup);
_ ->
ok
end,
maybe_started(Res)
end.
-spec start_listener(ref(), non_neg_integer(), module(), opts(), module(), any())
-> supervisor:startchild_ret().
start_listener(Ref, NumAcceptors, Transport, TransOpts0, Protocol, ProtoOpts)
when is_integer(NumAcceptors), is_atom(Transport), is_atom(Protocol) ->
TransOpts = normalize_opts(TransOpts0),
start_listener(Ref, Transport, TransOpts#{num_acceptors => NumAcceptors},
Protocol, ProtoOpts).
Ref, %% ets表中的key,相当于启动服务的key信息
NumAcceptors, %% 可接收最大的Accept数量
Transport, %% 处理 TCP/IP sockets的接口模块 一般使用ranch_tcp
TransOpts0, %% TCP/IP sockets的参数列表
Protocol, %% 回调模块
ProtoOpts %% 回调参数列表
主要看这行
Res = supervisor:start_child(ranch_sup, child_spec(Ref,
Transport, TransOpts, Protocol, ProtoOpts)),
-spec child_spec(ref(), module(), opts(), module(), any())
-> supervisor:child_spec().
child_spec(Ref, Transport, TransOpts0, Protocol, ProtoOpts) ->
TransOpts = normalize_opts(TransOpts0),
{{ranch_listener_sup, Ref}, {ranch_listener_sup, start_link, [
Ref, Transport, TransOpts, Protocol, ProtoOpts
]}, permanent, infinity, supervisor, [ranch_listener_sup]}.
-spec child_spec(ref(), non_neg_integer(), module(), opts(), module(), any())
-> supervisor:child_spec().
child_spec(Ref, NumAcceptors, Transport, TransOpts0, Protocol, ProtoOpts)
when is_integer(NumAcceptors), is_atom(Transport), is_atom(Protocol) ->
TransOpts = normalize_opts(TransOpts0),
child_spec(Ref, Transport, TransOpts#{num_acceptors => NumAcceptors},
Protocol, ProtoOpts).
这代码主要是在ranch_sup启动ranch_listener_sup这个supervisor
那就看ranch_listener_sup代码
-module(ranch_listener_sup).
-behaviour(supervisor).
-export([start_link/5]).
-export([init/1]).
-spec start_link(ranch:ref(), module(), any(), module(), any())
-> {ok, pid()}.
start_link(Ref, Transport, TransOpts, Protocol, ProtoOpts) ->
MaxConns = maps:get(max_connections, TransOpts, 1024),
ranch_server:set_new_listener_opts(Ref, MaxConns, TransOpts, ProtoOpts,
[Ref, Transport, TransOpts, Protocol, ProtoOpts]),
supervisor:start_link(?MODULE, {
Ref, Transport, Protocol
}).
init({Ref, Transport, Protocol}) ->
ok = ranch_server:set_listener_sup(Ref, self()),
ChildSpecs = [
{ranch_conns_sup, {ranch_conns_sup, start_link,
[Ref, Transport, Protocol]},
permanent, infinity, supervisor, [ranch_conns_sup]},
{ranch_acceptors_sup, {ranch_acceptors_sup, start_link,
[Ref, Transport]},
permanent, infinity, supervisor, [ranch_acceptors_sup]}
],
{ok, {{rest_for_one, 1, 5}, ChildSpecs}}.
里面就是启动了ranch_conns_sup 和 ranch_acceptors_sup
看下 ranch_conns_sup 这个监督进程代码
-spec start_link(ranch:ref(), module(), module()) -> {ok, pid()}.
start_link(Ref, Transport, Protocol) ->
proc_lib:start_link(?MODULE, init,
[self(), Ref, Transport, Protocol]).
loop(State=#state{parent=Parent, ref=Ref, conn_type=ConnType,
transport=Transport, protocol=Protocol, opts=Opts,
max_conns=MaxConns, logger=Logger}, CurConns, NbChildren, Sleepers) ->
receive
{?MODULE, start_protocol, To, Socket} ->
try Protocol:start_link(Ref, Socket, Transport, Opts) of
{ok, Pid} ->
handshake(State, CurConns, NbChildren, Sleepers, To, Socket, Pid, Pid);
{ok, SupPid, ProtocolPid} when ConnType =:= supervisor ->
handshake(State, CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid);
Ret ->
To ! self(),
ranch:log(error,
"Ranch listener ~p connection process start failure; "
"~p:start_link/4 returned: ~999999p~n",
[Ref, Protocol, Ret], Logger),
Transport:close(Socket),
loop(State, CurConns, NbChildren, Sleepers)
catch Class:Reason ->
To ! self(),
ranch:log(error,
"Ranch listener ~p connection process start failure; "
"~p:start_link/4 crashed with reason: ~p:~999999p~n",
[Ref, Protocol, Class, Reason], Logger),
loop(State, CurConns, NbChildren, Sleepers)
end;
{?MODULE, active_connections, To, Tag} ->
To ! {Tag, CurConns},
loop(State, CurConns, NbChildren, Sleepers);
%% Remove a connection from the count of connections.
{remove_connection, Ref, Pid} ->
case put(Pid, removed) of
active ->
loop(State, CurConns - 1, NbChildren, Sleepers);
remove ->
loop(State, CurConns, NbChildren, Sleepers);
undefined ->
_ = erase(Pid),
loop(State, CurConns, NbChildren, Sleepers)
end;
%% Upgrade the max number of connections allowed concurrently.
%% We resume all sleeping acceptors if this number increases.
{set_max_conns, MaxConns2} when MaxConns2 > MaxConns ->
_ = [To ! self() || To <- Sleepers],
loop(State#state{max_conns=MaxConns2},
CurConns, NbChildren, []);
{set_max_conns, MaxConns2} ->
loop(State#state{max_conns=MaxConns2},
CurConns, NbChildren, Sleepers);
%% Upgrade the protocol options.
{set_opts, Opts2} ->
loop(State#state{opts=Opts2},
CurConns, NbChildren, Sleepers);
{'EXIT', Parent, Reason} ->
terminate(State, Reason, NbChildren);
{'EXIT', Pid, Reason} when Sleepers =:= [] ->
case erase(Pid) of
active ->
report_error(Logger, Ref, Protocol, Pid, Reason),
loop(State, CurConns - 1, NbChildren - 1, Sleepers);
removed ->
report_error(Logger, Ref, Protocol, Pid, Reason),
loop(State, CurConns, NbChildren - 1, Sleepers);
undefined ->
loop(State, CurConns, NbChildren, Sleepers)
end;
%% Resume a sleeping acceptor if needed.
{'EXIT', Pid, Reason} ->
case erase(Pid) of
active when CurConns > MaxConns ->
report_error(Logger, Ref, Protocol, Pid, Reason),
loop(State, CurConns - 1, NbChildren - 1, Sleepers);
active ->
report_error(Logger, Ref, Protocol, Pid, Reason),
[To|Sleepers2] = Sleepers,
To ! self(),
loop(State, CurConns - 1, NbChildren - 1, Sleepers2);
removed ->
report_error(Logger, Ref, Protocol, Pid, Reason),
loop(State, CurConns, NbChildren - 1, Sleepers);
undefined ->
loop(State, CurConns, NbChildren, Sleepers)
end;
{system, From, Request} ->
sys:handle_system_msg(Request, From, Parent, ?MODULE, [],
{State, CurConns, NbChildren, Sleepers});
%% Calls from the supervisor module.
{'$gen_call', {To, Tag}, which_children} ->
Children = [{Protocol, Pid, ConnType, [Protocol]}
|| {Pid, Type} <- get(),
Type =:= active orelse Type =:= removed],
To ! {Tag, Children},
loop(State, CurConns, NbChildren, Sleepers);
{'$gen_call', {To, Tag}, count_children} ->
Counts = case ConnType of
worker -> [{supervisors, 0}, {workers, NbChildren}];
supervisor -> [{supervisors, NbChildren}, {workers, 0}]
end,
Counts2 = [{specs, 1}, {active, NbChildren}|Counts],
To ! {Tag, Counts2},
loop(State, CurConns, NbChildren, Sleepers);
{'$gen_call', {To, Tag}, _} ->
To ! {Tag, {error, ?MODULE}},
loop(State, CurConns, NbChildren, Sleepers);
Msg ->
ranch:log(error,
"Ranch listener ~p received unexpected message ~p~n",
[Ref, Msg], Logger),
loop(State, CurConns, NbChildren, Sleepers)
end.
启动完,在loop函数等待接收数据
看启动 ranch_acceptors_sup 代码
start_link(Ref, Transport) ->
supervisor:start_link(?MODULE, [Ref, Transport]).
init([Ref, Transport]) ->
ConnsSup = ranch_server:get_connections_sup(Ref),
TransOpts = ranch_server:get_transport_options(Ref),
NumAcceptors = maps:get(num_acceptors, TransOpts, 10),
Logger = maps:get(logger, TransOpts, error_logger),
LSocket = case maps:get(socket, TransOpts, undefined) of
undefined ->
SocketOpts = maps:get(socket_opts, TransOpts, []),
%% We temporarily put the logger in the process dictionary
%% so that it can be used from ranch:filter_options. The
%% interface as it currently is does not allow passing it
%% down otherwise.
put(logger, Logger),
case Transport:listen(SocketOpts) of
{ok, Socket} ->
erase(logger),
Socket;
{error, Reason} ->
listen_error(Ref, Transport, SocketOpts, Reason, Logger)
end;
Socket ->
Socket
end,
{ok, Addr} = Transport:sockname(LSocket),
ranch_server:set_addr(Ref, Addr),
Procs = [
{{acceptor, self(), N}, {ranch_acceptor, start_link, [
LSocket, Transport, Logger, ConnsSup
]}, permanent, brutal_kill, worker, []}
|| N <- lists:seq(1, NumAcceptors)],
{ok, {{one_for_one, 1, 5}, Procs}}.
这段代码就是启动监听,产生监听socket,然后启动NumAcceptors个ranch_acceptor进程
start_link(LSocket, Transport, Logger, ConnsSup) ->
Pid = spawn_link(?MODULE, loop, [LSocket, Transport, Logger, ConnsSup]),
{ok, Pid}.
-spec loop(inet:socket(), module(), module(), pid()) -> no_return().
loop(LSocket, Transport, Logger, ConnsSup) ->
_ = case Transport:accept(LSocket, infinity) of
{ok, CSocket} ->
case Transport:controlling_process(CSocket, ConnsSup) of
ok ->
%% This call will not return until process has been started
%% AND we are below the maximum number of connections.
ranch_conns_sup:start_protocol(ConnsSup, CSocket);
{error, _} ->
Transport:close(CSocket)
end;
%% Reduce the accept rate if we run out of file descriptors.
%% We can't accept anymore anyway, so we might as well wait
%% a little for the situation to resolve itself.
{error, emfile} ->
ranch:log(warning,
"Ranch acceptor reducing accept rate: out of file descriptors~n",
[], Logger),
receive after 100 -> ok end;
%% Exit if the listening socket got closed.
{error, closed} ->
exit(closed);
%% Continue otherwise.
{error, _} ->
ok
end,
flush(Logger),
?MODULE:loop(LSocket, Transport, Logger, ConnsSup).
当有client连接过来,产生csocket,并把这个socket转交给ranch_conns_sup进程
ranch_conns_sup:start_protocol(ConnsSup, CSocket);
-spec start_protocol(pid(), inet:socket()) -> ok.
start_protocol(SupPid, Socket) ->
SupPid ! {?MODULE, start_protocol, self(), Socket},
receive SupPid -> ok end.
这是向ranch_conns_sup进程发送 {?MODULE, start_protocol, self(), Socket}信息
然后看下收到消息的逻辑
loop(State=#state{parent=Parent, ref=Ref, conn_type=ConnType,
transport=Transport, protocol=Protocol, opts=Opts,
max_conns=MaxConns, logger=Logger}, CurConns, NbChildren, Sleepers) ->
receive
{?MODULE, start_protocol, To, Socket} ->
try Protocol:start_link(Ref, Socket, Transport, Opts) of
{ok, Pid} ->
handshake(State, CurConns, NbChildren, Sleepers, To, Socket, Pid, Pid);
这里就是启动回调模块的进程,
handshake(State=#state{ref=Ref, transport=Transport, handshake_timeout=HandshakeTimeout,
max_conns=MaxConns}, CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid) ->
case Transport:controlling_process(Socket, ProtocolPid) of
ok ->
ProtocolPid ! {handshake, Ref, Transport, Socket, HandshakeTimeout},
put(SupPid, active),
CurConns2 = CurConns + 1,
if CurConns2 < MaxConns ->
To ! self(),
loop(State, CurConns2, NbChildren + 1, Sleepers);
true ->
loop(State, CurConns2, NbChildren + 1, [To|Sleepers])
end;
{error, _} ->
Transport:close(Socket),
%% Only kill the supervised pid, because the connection's pid,
%% when different, is supposed to be sitting under it and linked.
exit(SupPid, kill),
To ! self(),
loop(State, CurConns, NbChildren, Sleepers)
end.
这段就是把csocket转交给回调的进程,
注意下,有可能controlling_process失败,所以需要在回调进程调用
ranch:handshake,然后ProtocolPid ! {handshake, Ref, Transport, Socket, HandshakeTimeout},
然后判断连接数,是否阻塞
有小伙伴问:
为什么需要从ranch_acceptor转到ranch_conns_sup再转到回调进程
1、当连接后,立马有大量的数据发过来,所有数据都堵塞在ranch_acceptor进程
2、需要判断连接数是否超出最大值