代码改变世界

mochiweb 源码阅读(十二)

2012-07-29 14:02  rhinovirus  阅读(1948)  评论(2编辑  收藏  举报

  今天也不上班,在家歇着,早起看看书,看看mochiweb源码,这一篇接着昨晚的那篇继续跟大家分享,我们从昨天没看完的mochiweb_acceptor:init/3函数继续往下看:

init(Server, Listen, Loop) ->
    T1 = now(),
    case catch mochiweb_socket:accept(Listen) of
        {ok, Socket} ->
            gen_server:cast(Server, {accepted, self(), timer:now_diff(now(), T1)}),
            call_loop(Loop, Socket);
        {error, closed} ->
            exit(normal);
        {error, timeout} ->
            init(Server, Listen, Loop);
        {error, esslaccept} ->
            exit(normal);
        Other ->
            error_logger:error_report(
              [{application, mochiweb},
               "Accept failed error",
               lists:flatten(io_lib:format("~p", [Other]))]),
            exit({error, accept_failed})
    end.

  昨天说完了erlang:now/0函数,今天我们从mochiweb_socket:accept/1这一行开始,先看下这个函数的完整代码:

accept({ssl, ListenSocket}) ->
    % There's a bug in ssl:transport_accept/2 at the moment, which is the
    % reason for the try...catch block. Should be fixed in OTP R14.
    try ssl:transport_accept(ListenSocket) of
        {ok, Socket} ->
            case ssl:ssl_accept(Socket) of
                ok ->
                    {ok, {ssl, Socket}};
                {error, _} = Err ->
                    Err
            end;
        {error, _} = Err ->
            Err
    catch
        error:{badmatch, {error, Reason}} ->
            {error, Reason}
    end;
accept(ListenSocket) ->
    gen_tcp:accept(ListenSocket, ?ACCEPT_TIMEOUT).

  这个函数有2个分支,第一个匹配SSL协议,第二个则是普通的Socket;我们看下第一个分支的注释,大意是,作者编写这个函数的时候,ssl:transport_accept/2有一个bug,这是添加try... catch块的原因。这个bug在R14版本是固定的。

  我们先看下函数:ssl:transport_accept/1,erlang doc 地址:http://www.erlang.org/doc/man/ssl.html#transport_accept-1,如下图:  

  大致翻译:在侦听套接字上接受传入的连接请求。ListenSocket必须是一个来自listen/2返回的套接字。返回的套接字必须通过ssl_accept完成SSL握手并建立连接。
  提醒:返回的套接字只能用于ssl_accept函数,调用之前是没有发送和接受流量的。
  接受套接字继承listen/2为ListenSocket设定的选项。
  默认的Timeout值是infinity,如果指定了Timeout,给定时间没有连接被接受,则返回{error, timeout}。

  好了,这个函数的注释还是比较多的。英文好的看英文,英文不好的,凑合看我的翻译吧,呵呵。本人英文实在太烂了,要读懂这些还需要借助谷歌翻译,惭愧。

  继续回到mochiweb_socket:accept/1函数,我们可以看到,调用上面这个函数的时候,并没有传递Timeout参数,也就没有超时处理,而是返回两种情况,一种是调用成功,返回{ok, NewSocket},另一种是调用失败返回{error, Reason}。

  当返回正确的{ok, Socket}时,这里将调用系统函数ssl:ssl_accept/1,这和之前咱们看到的ssl:transport_accept/1函数的注释是一致的:

            case ssl:ssl_accept(Socket) of
                ok ->
                    {ok, {ssl, Socket}};
                {error, _} = Err ->
                    Err
            end;

  这里,我们还是先查阅下erlang doc,地址:http://www.erlang.org/doc/man/ssl.html#ssl_accept-2,如下图:

  大致翻译:ssl_accept函数在服务器端建立SSL连接催生服务器循环它应该在调用transport_accept函数后直接调用。

  我的理解是,这里返回的套接字才真正完成完成SSL握手并建立连接,才可以接受和发送数据包。

  好了,到目前为止,第一个分支,我们算是分解完了;第二个分支明显就简单多了,只有一行代码:

gen_tcp:accept(ListenSocket, ?ACCEPT_TIMEOUT).

  依然查阅erlang doc,地址:http://www.erlang.org/doc/man/gen_tcp.html#accept-2,如下图:

  大致翻译:

  在侦听套接字上接受传入的连接请求Socket是必须listen/2返回一个套接字。Timeout指定超时值毫秒,默认为infinity

  如果建立了连接返回{ok, Socket};如果ListenSocket关闭,则返回{error, closed}如果在指定时间内没有建立连接,返回{error, timeout}如果在Erlang模拟器所有可用的端口都在使用,返回{error, system_limit}; ,如果发生其他错误,也可能返回一个POSIX错误,查看 inet(3)可能产生的错误值。

  数据包可以通过返回的套接字Socket使用send/2发送。数据包以下面的消息发送:{tcp, Socket, Data}。

  如果在监听的套接字选项列表中存在{active, false}选项,这种情况下,调用recv/2接受包

  好了,这个分支我们也弄明白了,需要特别注意的是这里指定了超时时间:?ACCEPT_TIMEOUT,为2000毫秒,这个宏定义如下:

-define(ACCEPT_TIMEOUT, 2000).

  其实这个函数逻辑很简单,就是当有客户端连接到服务器时,服务器就会接受到来自客户端的Socket连接,无论是使用SSL协议,还是普通的Socket。现在可以回到mochiweb_acceptor:init/3函数了,接下来的逻辑就是关于上面mochiweb_socket:accept/1函数返回值的处理:

        {ok, Socket} ->
            gen_server:cast(Server, {accepted, self(), timer:now_diff(now(), T1)}),
            call_loop(Loop, Socket);
        {error, closed} ->
            exit(normal);
        {error, timeout} ->
            init(Server, Listen, Loop);
        {error, esslaccept} ->
            exit(normal);
        Other ->
            error_logger:error_report(
              [{application, mochiweb},
               "Accept failed error",
               lists:flatten(io_lib:format("~p", [Other]))]),
            exit({error, accept_failed})

  好了,这一篇就到这里,关于返回值的处理,我们留到下一篇跟大家来分享。

  谢谢大家的耐心阅读。