看来昨天的大雨给北京确实带来了重创,早上出门去海淀驾校,北清路辛庄桥路段直接就堵死了,结果好不容易慢慢走到红绿灯那,才发现前方正抽水,封路了。唉,晚上回到家,上微博发现此次因灾遇难者37人,愿逝者安息!什么时候政府才能拿我们纳税人的钱办点正事,别整天搞形象工程了,来北京那么多年了,每次大雨都没能引起重视,也别搞什么引咎辞职了,直接抓了判刑吧。唉,牢骚发的有点多了,还是继续我们的主题吧,继续跟大家分享mochiweb源码。
在上一篇最后,我们提到mochiweb_http:start/1函数,其中就一行代码,如下:
start(Options) ->
mochiweb_socket_server:start(parse_options(Options)).
其中mochiweb_http:parse_options/1 函数我们在上一篇,已经详细分析了,这一篇,我们来看下函数 mochiweb_socket_server:start/1 代码如下:
start(Options) -> case lists:keytake(link, 1, Options) of {value, {_Key, false}, Options1} -> start_server(start, parse_options(Options1)); _ -> %% TODO: https://github.com/mochi/mochiweb/issues/58 %% [X] Phase 1: Add new APIs (Sep 2011) %% [_] Phase 2: Add deprecation warning %% [_] Phase 3: Change default to {link, false} and ignore link %% [_] Phase 4: Add deprecation warning for {link, _} option %% [_] Phase 5: Remove support for {link, _} option start_link(Options) end.
这里:
< Options = [{loop,{mochiweb_http,loop,
[#Fun<mochiweb_example_web.0.8815963>]}},
{name,mochiweb_example_web},
{ip,{0,0,0,0}},
{port,8080}]
首先,我们看下系统函数:lists:keytake/3,erlang doc地址:http://www.erlang.org/doc/man/lists.html#keytake-3,截图如下:
中文注释版本,参考:erlang lists 系列函数功能与用法详解(共68个函数),截图如下:
知道这个函数的作用,我们就能从上下文知道,如果Options列表中含有配置{link, false},则这里调用第一个分支:start_server(start, parse_options(Options1)),Options1为移除{link, false}选项后的Options列表,否则调用第二个分支:start_link(Options),函数代码如下:
start_link(Options) ->
start_server(start_link, parse_options(Options)).
现在我们来做个对比:
如果Options列表中含有配置{link, false},则调用:start_server(start, parse_options(Options1));
如果Options列表中含有配置{link, true},或者不含有该配置(这里我们可以理解为默认配置),则调用:start_server(start_link, parse_options(Options));
那么我们看下mochiweb_socket_server:start_server/2函数的具体实现:
start_server(F, State=#mochiweb_socket_server{ssl=Ssl, name=Name}) -> ok = prep_ssl(Ssl), case Name of undefined -> gen_server:F(?MODULE, State, []); _ -> gen_server:F(Name, ?MODULE, State, []) end.
这里F的值为start或start_link,现在大家对Options列表中是否含有link,且值是否为true是否有进一步的理解。其实这里很简单,该选项决定了以什么方式调用gen_server,如果你现在还对gen_server一无所知,建议你移步这里Gen_Server行为,希望你看完这个能对gen_server有所了解,这里也同时推荐《Erlang/OTP并发编程实战》这本书,目前我还没有看完,刚看到第二章,收获还是不少的。
好了,有个函数差点漏了,我们看下 mochiweb_socket_server:parse_options/1 这个函数,完整代码如下:
parse_options(State=#mochiweb_socket_server{}) -> State; parse_options(Options) -> parse_options(Options, #mochiweb_socket_server{}).
这里,如果配置为记录mochiweb_socket_server,则调用第一个分支,返回该配置。否则调用第二个分支,调用函数mochiweb_socket_server:parse_options/2传递Options为第一个参数,#mochiweb_socket_server{}记录为第二个参数,这个记录定义如下:
-record(mochiweb_socket_server, {port, loop, name=undefined, %% NOTE: This is currently ignored. max=2048, ip=any, listen=null, nodelay=false, backlog=128, active_sockets=0, acceptor_pool_size=16, ssl=false, ssl_opts=[{ssl_imp, new}], acceptor_pool=sets:new(), profile_fun=undefined}).
函数:mochiweb_socket_server:parse_options/2 完整代码如下:
parse_options([], State) -> State; parse_options([{name, L} | Rest], State) when is_list(L) -> Name = {local, list_to_atom(L)}, parse_options(Rest, State#mochiweb_socket_server{name=Name}); parse_options([{name, A} | Rest], State) when A =:= undefined -> parse_options(Rest, State#mochiweb_socket_server{name=A}); parse_options([{name, A} | Rest], State) when is_atom(A) -> Name = {local, A}, parse_options(Rest, State#mochiweb_socket_server{name=Name}); parse_options([{name, Name} | Rest], State) -> parse_options(Rest, State#mochiweb_socket_server{name=Name}); parse_options([{port, L} | Rest], State) when is_list(L) -> Port = list_to_integer(L), parse_options(Rest, State#mochiweb_socket_server{port=Port}); parse_options([{port, Port} | Rest], State) -> parse_options(Rest, State#mochiweb_socket_server{port=Port}); parse_options([{ip, Ip} | Rest], State) -> ParsedIp = case Ip of any -> any; Ip when is_tuple(Ip) -> Ip; Ip when is_list(Ip) -> {ok, IpTuple} = inet_parse:address(Ip), IpTuple end, parse_options(Rest, State#mochiweb_socket_server{ip=ParsedIp}); parse_options([{loop, Loop} | Rest], State) -> parse_options(Rest, State#mochiweb_socket_server{loop=Loop}); parse_options([{backlog, Backlog} | Rest], State) -> parse_options(Rest, State#mochiweb_socket_server{backlog=Backlog}); parse_options([{nodelay, NoDelay} | Rest], State) -> parse_options(Rest, State#mochiweb_socket_server{nodelay=NoDelay}); parse_options([{acceptor_pool_size, Max} | Rest], State) -> MaxInt = ensure_int(Max), parse_options(Rest, State#mochiweb_socket_server{acceptor_pool_size=MaxInt}); parse_options([{max, Max} | Rest], State) -> error_logger:info_report([{warning, "TODO: max is currently unsupported"}, {max, Max}]), MaxInt = ensure_int(Max), parse_options(Rest, State#mochiweb_socket_server{max=MaxInt}); parse_options([{ssl, Ssl} | Rest], State) when is_boolean(Ssl) -> parse_options(Rest, State#mochiweb_socket_server{ssl=Ssl}); parse_options([{ssl_opts, SslOpts} | Rest], State) when is_list(SslOpts) -> SslOpts1 = [{ssl_imp, new} | proplists:delete(ssl_imp, SslOpts)], parse_options(Rest, State#mochiweb_socket_server{ssl_opts=SslOpts1}); parse_options([{profile_fun, ProfileFun} | Rest], State) when is_function(ProfileFun) -> parse_options(Rest, State#mochiweb_socket_server{profile_fun=ProfileFun}).
从具体的函数逻辑来看,其实这个函数也很简单,就是分别去匹配Options中的每个配置项,然后按照一定格式去存储到记录#mochiweb_socket_server{}中,详细的逻辑我就不一行行跟大家分析了。如果这里你遇到什么地方不理解,欢迎大家留言跟我交流。
我们回到mochiweb_socket_server:start_server/2函数详细分析下:
start_server(F, State=#mochiweb_socket_server{ssl=Ssl, name=Name}) -> ok = prep_ssl(Ssl), case Name of undefined -> gen_server:F(?MODULE, State, []); _ -> gen_server:F(Name, ?MODULE, State, []) end.
第一行,调用函数:mochiweb_socket_server:prep_ssl/1,完整代码如下:
prep_ssl(true) -> ok = mochiweb:ensure_started(crypto), ok = mochiweb:ensure_started(public_key), ok = mochiweb:ensure_started(ssl); prep_ssl(false) -> ok.
这里仅仅是判断配置项中是否使用SSL协议,关于这个协议,参考维基百科:安全套接层(Secure Sockets Layer,SSL),如果使用ssl,则对应启动这三个应用。
第二行,判断配置项中是否存在name定义,如果存则,则使用该名字作为gen_server的名称,否则不使用名称注册该gen_server。
当启动这个gen_server,最后会调用: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).
好了,这一篇就到这里,谢谢大家的耐心阅读,下一篇,我们就从这个函数继续和大家分享mochiweb源码。
明天还要继续练车,大家早点休息,好梦。