mochiweb 源碼閱讀(十四)


  大家好,下了一天雨,十分涼爽,繼續來看mochiweb源碼,這一篇,我們來消化下上一篇留下的問題。

  首先是mochiweb_socket_server:handle_cast/2關於{accepted, Pid, Timing}消息的處理:

handle_cast({accepted, Pid, Timing},
            State=#mochiweb_socket_server{active_sockets=ActiveSockets}) ->
    State1 = State#mochiweb_socket_server{active_sockets=1 + ActiveSockets},
    case State#mochiweb_socket_server.profile_fun of
        undefined ->
            undefined;
        F when is_function(F) ->
            catch F([{timing, Timing} | state_to_proplist(State1)])
    end,
    {noreply, recycle_acceptor(Pid, State1)};

  這個分支比較簡單,首先,修改了State#mochiweb_socket_server記錄active_sockets字段的值,增加1;

  接着判斷是否定義了State#mochiweb_socket_server.profile_fun字段,如果之前的配置文件中存在該選項,且該選項是函數,則調用該函數,傳遞類似這樣的參數值:[{name, Name}, {port, Port}, {active_sockets, ActiveSockets}, {timing, Timing}]。

  而mochiweb_socket_server:state_to_proplist/1函數代碼如下,一眼就能看明白:

state_to_proplist(#mochiweb_socket_server{name=Name,
                                          port=Port,
                                          active_sockets=ActiveSockets}) ->
    [{name, Name}, {port, Port}, {active_sockets, ActiveSockets}].

  關於這個配置項,我們可以從mochiweb_http:start/1函數的注釋上了解到,如下:

%% @spec start(Options) -> ServerRet
%%     Options = [option()]
%%     Option = {name, atom()} | {ip, string() | tuple()} | {backlog, integer()}
%%              | {nodelay, boolean()} | {acceptor_pool_size, integer()}
%%              | {ssl, boolean()} | {profile_fun, undefined | (Props) -> ok}
%%              | {link, false}
%% @doc Start a mochiweb server.
%%      profile_fun is used to profile accept timing.
%%      After each accept, if defined, profile_fun is called with a proplist of a subset of the mochiweb_socket_server state and timing information.
%%      The proplist is as follows: [{name, Name}, {port, Port}, {active_sockets, ActiveSockets}, {timing, Timing}].
%% @end
start(Options) ->
    mochiweb_socket_server:start(parse_options(Options)).

  這里我們修改下mochiweb_example_web:start/1,增加profile_fun選項來測試下這個功能,代碼如下:

start(Options) ->
    {DocRoot, Options1} = get_option(docroot, Options),
    Loop = fun (Req) ->
                   ?MODULE:loop(Req, DocRoot)
           end,
    Profile_fun = fun (Proplist) -> 
              io:format("Proplist = ~p~n", [Proplist]) 
          end,
    mochiweb_http:start([{name, ?MODULE}, {loop, Loop}, {profile_fun, Profile_fun} | Options1]).

  重新編譯並啟動,然后用瀏覽器訪問:http://127.0.0.1:8080/,接着我們就能看到如下打印結果了:

  

  最后該分支返回如下結果:{noreply, recycle_acceptor(Pid, State1)};

  這里我們主要看下:mochiweb_socket_server:recycle_acceptor/2函數:

recycle_acceptor(Pid, State=#mochiweb_socket_server{
                        acceptor_pool=Pool,
                        listen=Listen,
                        loop=Loop,
                        active_sockets=ActiveSockets}) ->
    case sets:is_element(Pid, Pool) of
        true ->
            Acceptor = mochiweb_acceptor:start_link(self(), Listen, Loop),
            Pool1 = sets:add_element(Acceptor, sets:del_element(Pid, Pool)),
            State#mochiweb_socket_server{acceptor_pool=Pool1};
        false ->
            State#mochiweb_socket_server{active_sockets=ActiveSockets - 1}
    end.

  可以看到,這個函數最后返回State#mochiweb_socket_server記錄;

  首先,調用sets:is_element/2判斷Pid是否在Pool中存在;如果存在,則調用mochiweb_acceptor:start_link/3生成一個新的acceptor進程,並從Pool中移除Pid,添加新的acceptor進程,最后修改State#mochiweb_socket_server.acceptor_pool字段的值。

  如果不存在,則修改State#mochiweb_socket_server.active_sockets的值減少1。

  注意:這里可以看出每當有個客戶端連接上來,Pool池就會把當前負責的acceptor進程移除,同時添加一個新的acceptor進程,也就是保證Pool池,始終和啟動時擁有的數量一致。

  第一個問題解決了,接下來是第二個問題,調用mochiweb_http:loop/2函數,完整代碼如下:

loop(Socket, Body) ->
    ok = mochiweb_socket:setopts(Socket, [{packet, http}]),
    request(Socket, Body).

  從上下文,我們可以知道這里的Body是個匿名函數,它就是mochiweb_example_web:start/1定義的Loop變量,大家可以翻看前一篇文章,回憶下。

  這個函數,首先調用mochiweb_socket:setopts/2修改Socket的配置項,該函數完整代碼如下:

setopts({ssl, Socket}, Opts) ->
    ssl:setopts(Socket, Opts);
setopts(Socket, Opts) ->
    inet:setopts(Socket, Opts).

  同樣是分SSL協議和非SSL協議來調用不同系統函數設置。

  erlang doc 地址:http://www.erlang.org/doc/man/ssl.html#setopts-2http://www.erlang.org/doc/man/inet.html#setopts-2

  關於{packet, http}選項,大家可以查看上面第二個地址的文檔,如下圖:

  最后調用函數mochiweb_http:request/2

request(Socket, Body) ->
    ok = mochiweb_socket:setopts(Socket, [{active, once}]),
    receive
        {Protocol, _, {http_request, Method, Path, Version}} when Protocol == http orelse Protocol == ssl ->
            ok = mochiweb_socket:setopts(Socket, [{packet, httph}]),
            headers(Socket, {Method, Path, Version}, [], Body, 0);
        {Protocol, _, {http_error, "\r\n"}} when Protocol == http orelse Protocol == ssl ->
            request(Socket, Body);
        {Protocol, _, {http_error, "\n"}} when Protocol == http orelse Protocol == ssl ->
            request(Socket, Body);
        {tcp_closed, _} ->
            mochiweb_socket:close(Socket),
            exit(normal);
        {ssl_closed, _} ->
            mochiweb_socket:close(Socket),
            exit(normal);
        _Other ->
            handle_invalid_request(Socket)
    after ?REQUEST_RECV_TIMEOUT ->
        mochiweb_socket:close(Socket),
        exit(normal)
    end.

  好了,這一篇就到這里,這個函數我們留到下一篇繼續跟大家分享。

  晚安。


免責聲明!

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



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