周末两天在公司盯着服务器,平均在线突破500,服务器CPU,内存,带宽,还算正常,出了不少bug,周一继续修改。回到家,继续跟大家分享 cowboy。
上一篇讲到了 cowboy_listener 这个工作进程,今天接着讲上次提到的另外两个监控督程,先看第一个:
-module(cowboy_requests_sup). -behaviour(supervisor). -export([start_link/0, start_request/5]). %% API. -export([init/1]). %% supervisor. %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link(?MODULE, []). -spec start_request(pid(), inet:socket(), module(), module(), any()) -> {ok, pid()}. start_request(ListenerPid, Socket, Transport, Protocol, Opts) -> Protocol:start_link(ListenerPid, Socket, Transport, Opts). %% supervisor. -spec init([]) -> {'ok', {{'simple_one_for_one', 0, 1}, [{ any(), {atom() | tuple(), atom(), 'undefined' | [any()]}, 'permanent' | 'temporary' | 'transient', 'brutal_kill' | 'infinity' | non_neg_integer(), 'supervisor' | 'worker', 'dynamic' | [atom() | tuple()]}] }}. init([]) -> {ok, {{simple_one_for_one, 0, 1}, [{?MODULE, {?MODULE, start_request, []}, temporary, brutal_kill, worker, [?MODULE]}]}}.
这个督程比较简单,主要看下 init/1 方法, start_request/5 以后用到的时候再看。
Erlang OTP 设计规则对 simple_one_for_one 的解释: simple_one_for_one 重启规则的督程就是一个简化的 one_for_one 督程,
其中所有的子进程都是动态添加的同一个进程的实例。这边谢谢 0xcc,陈星 dp,成立涛 这三位朋友的耐心解答,使我理解了 simple_one_for_one 督程如何使用的。
如果使用 simple_one_for_one 启动的督程,在init/1 中并不真正启动子进程,而是定义了子名称的 MFA的规范。这边比较难理解,
希望大家搜索下跟 simple_one_for_one 相关的知识,增进下对 simple_one_for_one的理解。
start_request/5 这个方法这里先省略过去,增加调用时候,我会结合上下文做下详细的解释。
接下来我们来看下另一个督程,完整代码如下:
-module(cowboy_acceptors_sup). -behaviour(supervisor). -export([start_link/7]). %% API. -export([init/1]). %% supervisor. %% API. -spec start_link(non_neg_integer(), module(), any(), module(), any(), pid(), pid()) -> {ok, pid()}. start_link(NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts, ListenerPid, ReqsPid) -> supervisor:start_link(?MODULE, [NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts, ListenerPid, ReqsPid]). %% supervisor. -spec init([any()]) -> {'ok', {{'one_for_one', 10, 10}, [{ any(), {atom() | tuple(), atom(), 'undefined' | [any()]}, 'permanent' | 'temporary' | 'transient', 'brutal_kill' | 'infinity' | non_neg_integer(), 'supervisor' | 'worker', 'dynamic' | [atom() | tuple()]}] }}. init([NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts, ListenerPid, ReqsPid]) -> {ok, LSocket} = Transport:listen(TransOpts), Procs = [{{acceptor, self(), N}, {cowboy_acceptor, start_link, [ LSocket, Transport, Protocol, ProtoOpts, ListenerPid, ReqsPid ]}, permanent, brutal_kill, worker, []} || N <- lists:seq(1, NbAcceptors)], {ok, {{one_for_one, 10, 10}, Procs}}.
为了方便大家查看,我这边继续把详细参数列出来:
NbAcceptors = 100
Transport = cowboy_tcp_transport
TransOpts = [{port, 8080}]
Protocol = cowboy_http_protocol
ProtoOpts = [{dispatch, Dispatch}]
另外2个参数,咱们看下面图就很明白了:
ListenerPid 是cowboy_listener工作进程的进程标识;
ReqsPid 则是cowboy_requests_sup督程的进程标识;
supervisor:start_link(?MODULE, [NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts, ListenerPid, ReqsPid]).
这行就是启动督程,不详细解释了。看下 init/1这个方法,完整代码如下:
init([NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts, ListenerPid, ReqsPid]) -> {ok, LSocket} = Transport:listen(TransOpts), Procs = [{{acceptor, self(), N}, {cowboy_acceptor, start_link, [ LSocket, Transport, Protocol, ProtoOpts, ListenerPid, ReqsPid ]}, permanent, brutal_kill, worker, []} || N <- lists:seq(1, NbAcceptors)], {ok, {{one_for_one, 10, 10}, Procs}}.
这个方法比较有的说了,慢慢来吧,
我们先看第一行:
{ok, LSocket} = Transport:listen(TransOpts), 为了方便,把传递进来的参数带上去看看,如下:
{ok, LSocket} = cowboy_tcp_transport:listen([{port, 8080}]), 哇,是不是现在就很明白了,
那么我们看下 cowboy_tcp_transport:listen/1 方法吧,代码如下:
%% @doc Setup a socket to listen on the given port on the local host. %% %% The available options are: %% <dl> %% <dt>port</dt><dd>Mandatory. TCP port number to open.</dd> %% <dt>backlog</dt><dd>Maximum length of the pending connections queue. %% Defaults to 1024.</dd> %% <dt>ip</dt><dd>Interface to listen on. Listen on all interfaces %% by default.</dd> %% </dl> %% %% @see gen_tcp:listen/2 -spec listen([{port, inet:port_number()} | {ip, inet:ip_address()}]) -> {ok, inet:socket()} | {error, atom()}. listen(Opts) -> {port, Port} = lists:keyfind(port, 1, Opts), Backlog = proplists:get_value(backlog, Opts, 1024), ListenOpts0 = [binary, {active, false}, {backlog, Backlog}, {packet, raw}, {reuseaddr, true}], ListenOpts = case lists:keyfind(ip, 1, Opts) of false -> ListenOpts0; Ip -> [Ip|ListenOpts0] end, gen_tcp:listen(Port, ListenOpts).
这个方法里有不少函数,我们第一次看到,没关系,有强大的谷歌和erlang doc在手,我们继续:
{port, Port} = lists:keyfind(port, 1, Opts), 从Opts参数中,在这里就是 [{port, 8080}] 。
erlang doc 地址:http://www.erlang.org/doc/man/lists.html#keyfind-3 这里,我们读取 Port = 8080。
keyfind(Key, N, TupleList) -> Tuple | false
Types:
Searches the list of tuples TupleList for a tuple whose Nth element compares equal to Key. Returns Tuple if such a tuple is found, otherwise false.
Backlog = proplists:get_value(backlog, Opts, 1024), 这个函数我们之前讲过一次,从 Opts中,查找backlog的值,如果没有则为 1024。
ListenOpts0 = [binary, {active, false}, {backlog, Backlog}, {packet, raw}, {reuseaddr, true}], 这行不详细讲了。
ListenOpts = case lists:keyfind(ip, 1, Opts) of false -> ListenOpts0; Ip -> [Ip|ListenOpts0] end, 从列表中查找 ip,如果不存在,
则使用 ListenOpts0,存在,添加到 ListenOpts0头部。
这个函数的重点:gen_tcp:listen(Port, ListenOpts). erlang doc 地址:http://www.erlang.org/doc/man/gen_tcp.html
大家可以看下这个,上面有很详细的说明,由于篇幅,我在这不详细讲了。Sets up a socket to listen on the port Port on the local host.
启动一个socket监听 8080端口,这边需要特别注意的是 ListenOpts 这个参数,配置比较复杂,需要你详细看下每个配置的作用。
如果你对 tcp 一点都不知道,那建议你了解下 tcp 的相关知识,其实如果你对 http协议很了解的话,你应该知道 我们这边就是使用 tcp来实现http协议的。
好了,这个方法弄明白了,我们回到 init/1这个方法,继续看下一行:
Procs = [{{acceptor, self(), N}, {cowboy_acceptor, start_link, [
LSocket, Transport, Protocol, ProtoOpts,
ListenerPid, ReqsPid
]}, permanent, brutal_kill, worker, []}
|| N <- lists:seq(1, NbAcceptors)],
这一行定义了子进程规格, 而且是 100 个工作进程的子进程规格。回忆下,子进程规格的格式,这里就不重复复述了。
{ok, {{one_for_one, 10, 10}, Procs}}.
这行不陌生,跳过。
我们将在下一篇,看下 cowboy_acceptor 这个子工作进程模块,今天就到这了,明天还要上班,同样的,谢谢大家的支持。