Cowboy 源碼分析(四)


  上一篇我們簡單介紹了 cowboy 以及 cowboy_examples 下載,編譯和運行,這篇我們來理解下 cowboy_examples 源碼。

  1. 改造部分模塊,使它符合OTP設計原則的應用,這點可能大家會比較疑惑,但是我之所以修改它,是為了大家更好的理解,我們都知道 OTP 應用(這里有點形式化,但是在初期方便新手找到入口),一般是三個文件,分別是 Application_app.erl,Application_sup.erl,和 Application.app.src(編譯后為 Application.app)。但是我們看這個例子,並沒有按照這種規范來命名,為了方便新手,我做出了下面幾部分的修改:

  A. 首先,新增 cowboy_examples_app.erl 文件,拷貝cowboy_examples.erl 中的內容到這個文件,刪除 start/0 方法,修改模塊名:

-module(cowboy_examples_app).
-behaviour(application).
-export([start/2, stop/1]).

start(_Type, _Args) ->
    Dispatch = [
        {'_', [
            {[<<"websocket">>], websocket_handler, []},
            {[<<"eventsource">>], eventsource_handler, []},
            {[<<"eventsource">>, <<"live">>], eventsource_emitter, []},
            {'_', default_handler, []}
        ]}
    ],
    cowboy:start_listener(my_http_listener, 100,
        cowboy_tcp_transport, [{port, 8080}],
        cowboy_http_protocol, [{dispatch, Dispatch}]
    ),
    cowboy:start_listener(my_https_listener, 100,
        cowboy_ssl_transport, [
            {port, 8443}, {certfile, "priv/ssl/cert.pem"},
            {keyfile, "priv/ssl/key.pem"}, {password, "cowboy"}],
        cowboy_http_protocol, [{dispatch, Dispatch}]
    ),
    cowboy_examples_sup:start_link().

stop(_State) ->
    ok.

  B. 刪除 cowboy_examples.erl 模塊中的 stop/1 和 start/2 方法,增加 stop/0 方法,具體代碼如下:

-module(cowboy_examples).
-export([start/0, stop/0]).

start() ->
    application:start(crypto),
    application:start(public_key),
    application:start(ssl),
    application:start(cowboy),
    application:start(cowboy_examples).

stop() ->
    application:stop(cowboy_examples).

  C. 修改 cowboy_examples_app.src 文件中應用的模塊名,代碼如下:

{application, cowboy_examples, [
    {description, "Examples for cowboy."},
    {vsn, "0.1.0"},
    {modules, []},
    {registered, [cowboy_examples_sup]},
    {applications, [
        kernel,
        stdlib,
        crypto,
        public_key,
        ssl,
        cowboy
    ]},
    {mod, {cowboy_examples_app, []}},
    {env, []}
]}.

  好了,經過上面的小改造,這個例子,已經符合 OTP 設計原則中的應用規范了。打開終端,我們重新編譯下:

  cd ~/Source/cowboy_examples

  make clean

  make

  如下圖:

  

  啟動:

  終端輸入:sh start.sh

  

  到這里,我們的小改造算是完成了。接下來,我們回到源碼分析上。

  2. cowboy_examples_app 詳解,調用 application:start(cowboy_examples). 時,會調用該模塊中 start/2 方法,代碼如下:

start(_Type, _Args) ->
    Dispatch = [
        {'_', [
            {[<<"websocket">>], websocket_handler, []},
            {[<<"eventsource">>], eventsource_handler, []},
            {[<<"eventsource">>, <<"live">>], eventsource_emitter, []},
            {'_', default_handler, []}
        ]}
    ],
    cowboy:start_listener(my_http_listener, 100,
        cowboy_tcp_transport, [{port, 8080}],
        cowboy_http_protocol, [{dispatch, Dispatch}]
    ),
    cowboy:start_listener(my_https_listener, 100,
        cowboy_ssl_transport, [
            {port, 8443}, {certfile, "priv/ssl/cert.pem"},
            {keyfile, "priv/ssl/key.pem"}, {password, "cowboy"}],
        cowboy_http_protocol, [{dispatch, Dispatch}]
    ).
    %%cowboy_examples_sup:start_link().

  我們發現,cowboy_examples_sup:start_link().啟動的監控進程是沒有用的,在這個例子。所以在這里我把它注釋了,其實在這里也是不規范的,先不管了。

  我們看下上面代碼 default_handler 模塊,這個模塊就是 http://localhost:8080/ 時響應 Hello world! 的 handler,代碼如下:

-module(default_handler).
-behaviour(cowboy_http_handler).
-export([init/3, handle/2, terminate/2]).

init({_Any, http}, Req, []) ->
    {ok, Req, undefined}.

handle(Req, State) ->
    {ok, Req2} = cowboy_http_req:reply(200, [], <<"Hello world!">>, Req),
    {ok, Req2, State}.

terminate(_Req, _State) ->
    ok.

  注意,上面標紅顏色的cowboy_http_handler,可能你也跟我一樣,很好奇,沒有見過 -behaviour(自定義行為)。我把它稱為 自定義行為,其實這中用法,有點類似面向對象中的接口,如果你的模塊中增加 -behaviour(自定義行為),那么該模塊需要實現 自定義行為中指定的方法。我們來看下cowboy_http_handler 這個模塊,它在 cowboy 源碼中,如下:

-module(cowboy_http_handler).

-export([behaviour_info/1]).

%% @private
-spec behaviour_info(_)
    -> undefined | [{handle, 2} | {init, 3} | {terminate, 2}, ...].
behaviour_info(callbacks) ->
    [{init, 3}, {handle, 2}, {terminate, 2}];
behaviour_info(_Other) ->
    undefined.

  哇,是不是很簡單,短短幾行,behaviour_info(callbacks)這個方法,定義了,實現自定義行為的模塊,需要實現的方法,我們可以看到 [{init, 3}, {handle, 2}, {terminate, 2}];分別是[{方法名, 參數個數}...] 這樣的格式來規范,看明白了 cowboy_http_handler,我們接着回到 default_handler 模塊。

  init/3 和 terminate/2 這兩個方法比較簡單,不介紹了;

  重點看下 handle/2 這個方法,如下:

  handle(Req, State) ->

    {ok, Req2} = cowboy_http_req:reply(200, [], <<"Hello world!">>, Req),

    {ok, Req2, State}.

  這個就是當我們訪問 http://localhost:8080/ 返回 Hello world! 的處理,我們暫時先不管。

  回到 cowboy_examples_app 的 start/2 函數,我們看下這段代碼:

%% @doc Start a listener for the given transport and protocol.
%%
%% A listener is effectively a pool of <em>NbAcceptors</em> acceptors.
%% Acceptors accept connections on the given <em>Transport</em> and forward
%% requests to the given <em>Protocol</em> handler. Both transport and protocol
%% modules can be given options through the <em>TransOpts</em> and the
%% <em>ProtoOpts</em> arguments. Available options are documented in the
%% <em>listen</em> transport function and in the protocol module of your choice.
%%
%% All acceptor and request processes are supervised by the listener.
%%
%% It is recommended to set a large enough number of acceptors to improve
%% performance. The exact number depends of course on your hardware, on the
%% protocol used and on the number of expected simultaneous connections.
%%
%% The <em>Transport</em> option <em>max_connections</em> allows you to define
%% the maximum number of simultaneous connections for this listener. It defaults
%% to 1024. See <em>cowboy_listener</em> for more details on limiting the number
%% of connections.
%%
%% Although Cowboy includes a <em>cowboy_http_protocol</em> handler, other
%% handlers can be created for different protocols like IRC, FTP and more.
%%
%% <em>Ref</em> can be used to stop the listener later on.
-spec start_listener(any(), non_neg_integer(), module(), any(), module(), any())
    -> {ok, pid()}.
start_listener(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts)
        when is_integer(NbAcceptors) andalso is_atom(Transport)
        andalso is_atom(Protocol) ->
    supervisor:start_child(cowboy_sup, child_spec(Ref, NbAcceptors,
        Transport, TransOpts, Protocol, ProtoOpts)).

%% @doc Return a child spec suitable for embedding.
%%
%% When you want to embed cowboy in another application, you can use this
%% function to create a <em>ChildSpec</em> suitable for use in a supervisor.
%% The parameters are the same as in <em>start_listener/6</em> but rather
%% than hooking the listener to the cowboy internal supervisor, it just returns
%% the spec.
-spec child_spec(any(), non_neg_integer(), module(), any(), module(), any())
    -> supervisor:child_spec().
child_spec(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts)
        when is_integer(NbAcceptors) andalso is_atom(Transport)
        andalso is_atom(Protocol) ->
    {{cowboy_listener_sup, Ref}, {cowboy_listener_sup, start_link, [
        NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts
    ]}, permanent, 5000, supervisor, [cowboy_listener_sup]}.

  該方法描述翻譯如下(很差勁的翻譯):

  開啟一個指定傳輸協議的監聽。

  NbAcceptors是一個有效的監聽接收器。接收器接受連接在給定的 Transport和發送請求到 Protocol handler。通過 TransOpts 和 ProtoOpts 參數可以給  transport 和 protocol 兩個模塊指定選擇項。在你選擇的 protocol 模塊,通過有效的選項監聽傳輸方法。

  通過 listener 監督所有的接收器和請求進程。

  這是建立在足夠大的數量來接收以提高性能。確切的數量當然取決於你的硬件。在一些預期的同時連接使用該協議

  在Transport中配置 max_connections 定義同時連接到 listener 的最大數量。默認值是 1024,查看 cowboy_listener 獲得更多詳細的連接數限制。

  雖然 Cowboy 包含一個 cowboy_http_protocol handler,更多類似 IRC, FTP 等不同協議的handlers可以創建。

  Ref 用來在之后用開停止 listener

  很抱歉,翻譯的很爛。

  這個函數的參數類型說明:

  -spec start_listener(any(), non_neg_integer(), module(), any(), module(), any())

  該例子具體給的參數如下:

  Ref = my_http_listener

  NbAcceptors = 100

  Transport = cowboy_tcp_transport

  TransOpts = [{port, 8080}]

  Protocol = cowboy_http_protocol

  ProtoOpts = [{dispatch, Dispatch}]

  這個方法里就一句話,supervisor:start_child這個方法用於給一個存在的督程動態添加子進程,也就是給 cowboy_sup這個監督進程,動態添加子對象,而 child_spec這個方法是對子進程的定義,如果你看過我之前的幾篇文章,其中一篇,提高很詳細的 OTP設計原則的子進程規格,這個方法就是構造這樣的規格。

  其實就是往 cowboy_sup 這個督程下,添加一個名為 cowboy_listener_sup 的督程:

  Id{cowboy_listener_sup, Ref}

  StartFunc = {M, F, A} = {cowboy_listener_sup, start_link, [NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts]}

  Restart = permanent

  Shutdown = 5000

  Type = supervisor

  Modules = [cowboy_listener_sup]

  好了,這個方法算是講完了,當我們調用 cowboy_listener_sup:start_link([NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts]).傳遞了這些參數。那么接下來,我們應該關注這個 cowboy_listener_sup這個模塊,它究竟是怎么跟自定義的協議等等關聯的呢。

  關注我的下一篇文章吧。

  很抱歉,這篇文章拖了好幾天,最近我們公司的游戲昨天剛上線就遇到了,接近宕機的bug,連夜解決了,今天在公司繼續盯着,找了點時間。寫完了這篇文章。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM