代码改变世界

mochiweb 源码阅读(十)

2012-07-27 01:19  rhinovirus  阅读(2178)  评论(0编辑  收藏  举报

  大家晚上好,这几天忙着练车,开车真是体力活呀,累的够呛,到家就洗洗睡了,因为是下午1点到8点练车,倒是给了我不少时间看书,《Erlang/OTP并发编程实战》这本书不建议当作入门Erlang书,建议先看完《Erlang程序设计》这本,也可以两本结合着看。不管怎么说,这本书确实很给力,学到不少知识。对了,顺便告诉所有Erlang开发者一个好消息,Learn You Some Erlang for Great Good! a Beginner's Guide这本书现在由淘宝褚霸来翻译,算是下一本比较值得期待的Erlang书吧。

  好了,回到今天的正题,我们继续来阅读mochiweb源码。

  上一篇我们补充分析了mochiweb_socket_server:parse_options/1函数,以及上上篇,我们讲到了mochiweb_socket_server:start_server/2函数,这个函数会启动gen_server容器,也就是调用gen_server的start或者start_link函数,而这个函数对应的回调函数为Module:init/1,也就是mochiweb_socket_server:init/1函数:

init(State=#mochiweb_socket_server{ip=Ip, port=Port, backlog=Backlog, nodelay=NoDelay}) ->
    process_flag(trap_exit, true),
    BaseOpts = [binary,
                {reuseaddr, true},
                {packet, 0},
                {backlog, Backlog},
                {recbuf, ?RECBUF_SIZE},
                {active, false},
                {nodelay, NoDelay}],
    Opts = case Ip of
        any ->
            case ipv6_supported() of % IPv4, and IPv6 if supported
                true -> [inet, inet6 | BaseOpts];
                _ -> BaseOpts
            end;
        {_, _, _, _} -> % IPv4
            [inet, {ip, Ip} | BaseOpts];
        {_, _, _, _, _, _, _, _} -> % IPv6
            [inet6, {ip, Ip} | BaseOpts]
    end,
    listen(Port, Opts, State).

  首先是:process_flag/2函数,erlang doc 地址:http://www.erlang.org/doc/man/erlang.html#process_flag-2,由于这部分说明较多,我只截了重要的一部分说明,如下图:

  翻译:调用这个函数设置进程一定的标志返回标志的值

  trap_exit设置为true退出信号到达一个进程转化为{'EXIT', From, Reason}消息,作为普通邮件可以收到。如果trap_exit设置为false,如果它收到normal退出信号或者非正常退出信号来源于链接到它的进程,进程将退出。

  这个其实也很简单,就是当设置为true时,你可以捕获退出信号,而下面代码就是处理退出信号的回调函数:  

handle_info({'EXIT', Pid, normal}, State) ->
    {noreply, recycle_acceptor(Pid, State)};
handle_info({'EXIT', Pid, Reason},
            State=#mochiweb_socket_server{acceptor_pool=Pool}) ->
    case sets:is_element(Pid, Pool) of
        true ->
            %% If there was an unexpected error accepting, log and sleep.
            error_logger:error_report({?MODULE, ?LINE,
                                       {acceptor_error, Reason}}),
            timer:sleep(100);
        false ->
            ok
    end,
    {noreply, recycle_acceptor(Pid, State)};

  大家可以先看一眼,我这里不详细讲这个,继续回到mochiweb_socket_server:init/1函数的下一行:

    BaseOpts = [binary,
                {reuseaddr, true},
                {packet, 0},
                {backlog, Backlog},
                {recbuf, ?RECBUF_SIZE},
                {active, false},
                {nodelay, NoDelay}],

  简单的构建了一个基础配置列表;继续往下看:

    Opts = case Ip of
        any ->
            case ipv6_supported() of % IPv4, and IPv6 if supported
                true -> [inet, inet6 | BaseOpts];
                _ -> BaseOpts
            end;
        {_, _, _, _} -> % IPv4
            [inet, {ip, Ip} | BaseOpts];
        {_, _, _, _, _, _, _, _} -> % IPv6
            [inet6, {ip, Ip} | BaseOpts]
    end,

  这部分是关于IPv4和IPv6的处理,首先是判断Ip是否为any,如果是则调用函数:mochiweb_socket_server:ipv6_supported/0,完整代码如下:

ipv6_supported() ->
    case (catch inet:getaddr("localhost", inet6)) of
        {ok, _Addr} ->
            true;
        {error, _} ->
            false
    end.

  我们先看个系统函数:inet:getaddr/2,erlang doc 地址:http://www.erlang.org/doc/man/inet.html#getaddr-2,如下图:

   测试结果如下图:

  

  其中error的具体含义,可以参考这里:http://www.erlang.org/doc/man/inet.html#error_codes,比如:

  einval - invalid argument (无效的参数)

  nxdomain - the hostname or domain name could not be found (无法找到主机名或域名)

  好了,弄清楚这个函数作用,mochiweb_socket_server:ipv6_supported/0就没什么问题了,我们还是回到mochiweb_socket_server:init/1函数,如果ipv6_supported/0返回true,说明是2种ip类型都是支持的。另外2个分支分别是ipv4和ipv6的不同情况处理。

  继续往下:

listen(Port, Opts, State).

  这里调用:mochiweb_socket_server:listen/3函数:  

listen(Port, Opts, State=#mochiweb_socket_server{ssl=Ssl, ssl_opts=SslOpts}) ->
    case mochiweb_socket:listen(Ssl, Port, Opts, SslOpts) of
        {ok, Listen} ->
            {ok, ListenPort} = mochiweb_socket:port(Listen),
            {ok, new_acceptor_pool(
                   Listen,
                   State#mochiweb_socket_server{listen=Listen,
                                                port=ListenPort})};
        {error, Reason} ->
            {stop, Reason}
    end.

  这个函数,首先调用mochiweb_socket:listen/4函数,下面是我列出调用这个函数时,具体参数的值:

< Port = 8080
< Opts = [inet,
          {ip,{0,0,0,0}},
          binary,
          {reuseaddr,true},
          {packet,0},
          {backlog,128},
          {recbuf,8192},
          {active,false},
          {nodelay,false}]
< Ssl = false < SslOpts = [{ssl_imp,new}]

  具体函数的代码如下:

listen(Ssl, Port, Opts, SslOpts) ->
    case Ssl of
        true ->
            case ssl:listen(Port, Opts ++ SslOpts) of
                {ok, ListenSocket} ->
                    {ok, {ssl, ListenSocket}};
                {error, _} = Err ->
                    Err
            end;
        false ->
            gen_tcp:listen(Port, Opts)
    end.

  这个函数的逻辑也很简单,首先判断是否使用SSL协议,如果不使用,则直接调用gen_tcp:listen/2传递端口以及配置开启tcp监听;如果使用,则调用ssl:listen/2开启SSL类型的Socket监听,特别注意这里返回:{ok, {ssl, ListenSocket}}而不是{ok, ListenSocket}。

  对应的erlang doc 地址:http://www.erlang.org/doc/man/gen_tcp.html#listen-2http://www.erlang.org/doc/man/ssl.html#listen-2;这里我就不截图了,内容较多。

  接着,我们继续回到mochiweb_socket_server:listen/3函数:

        {ok, Listen} ->
            {ok, ListenPort} = mochiweb_socket:port(Listen),
            {ok, new_acceptor_pool(
                   Listen,
                   State#mochiweb_socket_server{listen=Listen,
                                                port=ListenPort})};
        {error, Reason} ->
            {stop, Reason}

  如果返回 {error, Reason} 则立刻终止该 gen_server,如果返回 {ok, Listen},则表示监听成功。

  假如使用的是SSL协议,则 Listen = {ssl, ListenSocket};否则为 ListenSocket;

  我们继续往下看:{ok, ListenPort} = mochiweb_socket:port(Listen),这里调用函数mochiweb_socket:port/2,该函数完整代码如下:

port({ssl, Socket}) ->
    case ssl:sockname(Socket) of
        {ok, {_, Port}} ->
            {ok, Port};
        {error, _} = Err ->
            Err
    end;
port(Socket) ->
    inet:port(Socket).

  这里依然是分别处理SSL协议和非SSL协议两种情况,我们先看下这2个系统函数:

  函数:ssl:sockname/1,erlang doc 地址:http://www.erlang.org/doc/man/ssl.html#peername-1,如下图:

  返回该对等网络的地址和端口好。

  函数:inet:port/1,erlang doc 地址:http://www.erlang.org/doc/man/inet.html#port-1,如下图:

   返回该套接字的本地端口号。

  好了,这个函数我们就差这一段代码还没讲,我们留到下一篇来分解:

            {ok, new_acceptor_pool(
                   Listen,
                   State#mochiweb_socket_server{listen=Listen,
                                                port=ListenPort})};

  最后,谢谢大家的耐心阅读,晚安。