代码改变世界

Cowboy 源码分析(六)

2012-05-21 01:07  rhinovirus  阅读(2814)  评论(0编辑  收藏  举报

  周末两天在公司盯着服务器,平均在线突破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:

    Key = term()
    N = 1..tuple_size(Tuple)
    TupleList = [Tuple]
    Tuple = tuple()

  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 这个子工作进程模块,今天就到这了,明天还要上班,同样的,谢谢大家的支持。