今天也不上班,在家歇着,早起看看书,看看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})
好了,这一篇就到这里,关于返回值的处理,我们留到下一篇跟大家来分享。
谢谢大家的耐心阅读。