代码改变世界

Cowboy 源码分析(五)

2012-05-20 00:45  rhinovirus  阅读(3281)  评论(1编辑  收藏  举报

  大家好,这一篇是 cowboy 源码分析的第五篇文章了,可能我的写作能力不好,很多朋友看的比较迷糊,我也是尽力去说的更明白,希望越写越好吧。

  上一篇,我们讲到了 cowboy:child_spec/6 这个方法,这个方法返回 动态启动 cowboy_listener_sup 模块的子进程规格,然后通过添加到监督进程,并启动supervisor:start_child(cowboy_sup, child_spec(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts)).

  接下来我们看下cowboy_listener_sup 模块:

-module(cowboy_listener_sup).
-behaviour(supervisor).

-export([start_link/5]). %% API.
-export([init/1]). %% supervisor.

%% API.

-spec start_link(non_neg_integer(), module(), any(), module(), any())
    -> {ok, pid()}.
start_link(NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts) ->
    MaxConns = proplists:get_value(max_connections, TransOpts, 1024),
    {ok, SupPid} = supervisor:start_link(?MODULE, []),
    {ok, ListenerPid} = supervisor:start_child(SupPid,
        {cowboy_listener, {cowboy_listener, start_link, [MaxConns, ProtoOpts]},
         permanent, 5000, worker, [cowboy_listener]}),
    {ok, ReqsPid} = supervisor:start_child(SupPid,
        {cowboy_requests_sup, {cowboy_requests_sup, start_link, []},
         permanent, 5000, supervisor, [cowboy_requests_sup]}),
    {ok, _PoolPid} = supervisor:start_child(SupPid,
        {cowboy_acceptors_sup, {cowboy_acceptors_sup, start_link, [
            NbAcceptors, Transport, TransOpts,
            Protocol, ProtoOpts, ListenerPid, ReqsPid
        ]}, permanent, 5000, supervisor, [cowboy_acceptors_sup]}),
    {ok, SupPid}.

%% supervisor.

-spec init([]) -> {ok, {{one_for_all, 10, 10}, []}}.
init([]) ->
    {ok, {{one_for_all, 10, 10}, []}}.

  这个模块有2个函数,init/1 定义了 重启策略和最大重启频率,重点看下 start_link/5 这个方法:

  a. 首先:

    MaxConns = proplists:get_value(max_connections, TransOpts, 1024),

  这个方法我第一次遇到,查了下 erlang doc,地址:http://www.erlang.org/doc/man/proplists.html 

  proplists:get_value(Key, List, Default) -> term().

  Key = term()

  List = [term()]

  Default = term()

  Returns the value of a simple key/value property in List. If lookup(Key, List) would yield {Key, Value}, this function returns the corresponding Value, otherwise Default is returned.

  大概意思是从 List 中查找 Key ,并返回它的值,如果不存在,则使用 Default 值。
 
  b. 接下来看:
    {ok, SupPid} = supervisor:start_link(?MODULE, []),
  以 ?MODULE 为名称,创建一个督程。
 
  c. 接下来是:
    {ok, ListenerPid} = supervisor:start_child(SupPid,
        {cowboy_listener, {cowboy_listener, start_link, [MaxConns, ProtoOpts]},
         permanent, 5000, worker, [cowboy_listener]}),
  将 cowboy_listener 工作进程加入?MODULE督程树中,其他就不详细介绍了。
 
  d.紧接着:
    {ok, ReqsPid} = supervisor:start_child(SupPid,
        {cowboy_requests_sup, {cowboy_requests_sup, start_link, []},
         permanent, 5000, supervisor, [cowboy_requests_sup]}),
  将 cowboy_requests_sup 督程添加到?MODULE督程树中
 
  e. 然后:
    {ok, _PoolPid} = supervisor:start_child(SupPid,
        {cowboy_acceptors_sup, {cowboy_acceptors_sup, start_link, [
            NbAcceptors, Transport, TransOpts,
            Protocol, ProtoOpts, ListenerPid, ReqsPid
        ]}, permanent, 5000, supervisor, [cowboy_acceptors_sup]}),
  将cowboy_acceptors_sup 督程也添加到?MODULE督程中。
  f. 最后返回 {ok, SupPid}.
 
  好了,这个模块比较简单,建立一个监督树,然后添加一个工作进程,和两棵子监督进程,我们来看下 cowboy_listener 这个工作进程吧。
%% @private
%%
%% We set the process priority to high because cowboy_listener is the central
%% gen_server in Cowboy and is used to manage all the incoming connections.
%% Setting the process priority to high ensures the connection-related code
%% will always be executed when a connection needs it, allowing Cowboy to
%% scale far beyond what it would with a normal priority.
-spec start_link(non_neg_integer(), any()) -> {ok, pid()}.
start_link(MaxConns, ProtoOpts) ->
    gen_server:start_link(?MODULE, [MaxConns, ProtoOpts],
        [{spawn_opt, [{priority, high}]}]).

  我们看下 start_link/2 函数。这里 MaxConns = proplists:get_value(max_connections, TransOpts, 1024), ProtoOpts = [{dispatch, Dispatch}]。我们看下gen_server:start_link方法这2个参数的作用。erlang doc 地址:http://www.erlang.org/doc/man/gen_server.html

  第二个参数 Args = [MaxConns, ProtoOpts],官方文档对这个参数的解释:Args is an arbitrary term which is passed as the argument to Module:init/1. 大意是 Args 是一个任意的 term() 作为参数传递给 Module:init/1

  第三个参数 Options = [{spawn_opt, SOpts}] =  [{spawn_opt, [{priority, high}]}],官方文档对这个参数的解释:If the option {spawn_opt,SOpts} is present, SOpts will be passed as option list to the spawn_opt BIF which is used to spawn the gen_server. 大意如下:如果该选项 {spawn_opt,SOpts} 存在,SOpts 将通过该选项列表将作为 spawn_otp的参数。

  我看了下 OTP 源码,最后这个参数是出现在 proc_lib.erl 模块中,如下图:

  

  那么我们看下 elang:spawn_opt/4 这个方法究竟是啥意思。还是查 erlang doc,地址:http://www.erlang.org/doc/man/erlang.html#spawn_opt-4 传递这个参数,设置新创建的进程的优先级,相当于执行在进程的开始函数执行 process_flag(priority, Level) ,具体说明如下:

{priority, Level}

Sets the priority of the new process. Equivalent to executing process_flag(priority, Level) in the start function of the new process, except that the priority will be set before the process is selected for execution for the first time. For more information on priorities see process_flag(priority, Level).

  好了,跑了有点远了,继续回来说这个函数,其实,上面那个参数,通俗的理解就是设置进程优先级。

  接着回到 cowboy_listener,我们看下在调用 start_link 方法之后会调用的 init/1 方法,如下:
%% @private
-spec init(list()) -> {ok, #state{}}.
init([MaxConns, ProtoOpts]) ->
    ReqsTable = ets:new(requests_table, [set, private]),
    Queue = queue:new(),
    {ok, #state{reqs_table=ReqsTable, max_conns=MaxConns,
        proto_opts=ProtoOpts, queue=Queue}}.

  这个方法接受了一个参数,就是 start_link/3 的第二个参数,然后创建 ets表 requests_table,类型是[set, private];定义了一个先进先出的队列;最后返回 {ok, State}。State的记录类型是:

-record(state, {
    req_pools = [] :: pools(),
    reqs_table :: ets:tid(),
    queue = undefined :: queue(),
    max_conns = undefined :: non_neg_integer(),
    proto_opts :: any(),
    proto_opts_vsn = 1 :: non_neg_integer()
}).

  好了,这个模块,就先讲到这里,谢谢大家支持。