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-2,http://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})};
最后,谢谢大家的耐心阅读,晚安。
本文基于署名-非商业性使用 3.0许可协议发布,欢迎转载,演绎,但是必须保留本文的署名rhinovirus(包含链接http://www.cnblogs.com/rhinovirus/),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系。