Cowboy 源码分析(七)
2012-05-22 00:18 rhinovirus 阅读(2694) 评论(1) 编辑 收藏 举报上一篇我们讲到了cowboy_requests_sup这个模块,这一篇继续往下走,看下 cowboy_acceptor 这个模块:
%% @private -module(cowboy_acceptor). -export([start_link/6]). %% API. -export([acceptor/7]). %% Internal. %% API. -spec start_link(inet:socket(), module(), module(), any(), pid(), pid()) -> {ok, pid()}. start_link(LSocket, Transport, Protocol, Opts, ListenerPid, ReqsSup) -> Pid = spawn_link(?MODULE, acceptor, [LSocket, Transport, Protocol, Opts, 1, ListenerPid, ReqsSup]), {ok, Pid}. %% Internal. -spec acceptor(inet:socket(), module(), module(), any(), non_neg_integer(), pid(), pid()) -> no_return(). acceptor(LSocket, Transport, Protocol, Opts, OptsVsn, ListenerPid, ReqsSup) -> Res = case Transport:accept(LSocket, 2000) of {ok, CSocket} -> {ok, Pid} = supervisor:start_child(ReqsSup, [ListenerPid, CSocket, Transport, Protocol, Opts]), Transport:controlling_process(CSocket, Pid), cowboy_listener:add_connection(ListenerPid, default, Pid, OptsVsn); {error, timeout} -> cowboy_listener:check_upgrades(ListenerPid, OptsVsn); {error, _Reason} -> %% @todo Probably do something here. If the socket was closed, %% we may want to try and listen again on the port? ok end, case Res of ok -> ?MODULE:acceptor(LSocket, Transport, Protocol, Opts, OptsVsn, ListenerPid, ReqsSup); {upgrade, Opts2, OptsVsn2} -> ?MODULE:acceptor(LSocket, Transport, Protocol, Opts2, OptsVsn2, ListenerPid, ReqsSup) end.
我们先看下 spawn_link/4 这个函数,同样的,列出所有参数的值:
{ok, LSocket} = cowboy_tcp_transport:listen([{port, 8080}]),
Transport= cowboy_tcp_transport,
Protocol = cowboy_http_protocol,
Opts = [{dispatch, Dispatch}],
ListenerPid 是cowboy_listener工作进程的进程标识;
ReqsPid 则是cowboy_requests_sup督程的进程标识;
Pid = spawn_link(?MODULE, acceptor, [LSocket, Transport, Protocol, Opts, 1, ListenerPid, ReqsSup]), spawn_link/3
erlang doc 如下: http://www.erlang.org/doc/man/erlang.html#spawn_link-3 具体我贴过来了,如下:
spawn_link(Module, Function, Args) -> pid()
Types:
Returns the pid of a new process started by the application of Module:Function to Args. A link is created between the calling process and the new process, atomically. Otherwise works like spawn/3.
大意如下:创建一个进程,并执行 M:F(A),并返回进程id。新创建的进程链接到调用进程。
在这里,M:F(A) = ?MODULE:acceptor(LSocket, Transport, Protocol, Opts, 1, ListenerPid, ReqsSup),
这里注意下,我们
那么接下来我们看下 cowboy_acceptor:acceptor/7 方法:
Res = case Transport:accept(LSocket, 2000) of
也就是 Res = case cowboy_tcp_transport:accept(LSocket, 2000) of
我们看下这个模块的这个函数,通过socket监听接受到来的连接请求:
%% @doc Accept an incoming connection on a listen socket. %% @see gen_tcp:accept/2 -spec accept(inet:socket(), timeout()) -> {ok, inet:socket()} | {error, closed | timeout | atom()}. accept(LSocket, Timeout) -> gen_tcp:accept(LSocket, Timeout).
通过socket监听接受到来的连接请求。ListenSocket必须是由listen/2返回的。Timeout指定了一个毫秒级的超时,默认为infinity。
如果连接建立则返回{ok,Socket},如果ListenSocket关闭了则返回{error,closed},如果在指定的时间内没有建立连接, 则返回{error,timeout}。如果某些东西出错,也可能返回一个POSIX错误。可能的错误值请参考inet(3)。
erlang doc :http://www.erlang.org/doc/man/gen_tcp.html
有朋友翻译成中文版:http://blog.csdn.net/leechyu89/article/details/7305673 这边感谢这位素不相识的朋友。
接着回到 cowboy_acceptor:acceptor/7 方法:
{ok, CSocket} -> {ok, Pid} = supervisor:start_child(ReqsSup, [ListenerPid, CSocket, Transport, Protocol, Opts]), Transport:controlling_process(CSocket, Pid), cowboy_listener:add_connection(ListenerPid, default, Pid, OptsVsn);
在调用 case cowboy_tcp_transport:accept(LSocket, 2000) of 如果返回 {ok, CSocket}, 也就是和 LSocket 连接建立,则启动一个cowboy_requests_sup督程的子进程,代码如下:
{ok, Pid} = supervisor:start_child(ReqsSup, [ListenerPid, CSocket, Transport, Protocol, Opts]),
不知道大家还记得吗,上一篇,我们提到,cowboy_requests_sup督程,所有的子进程都是动态添加的同一个进程的实例,这边,我们再贴下代码:
init([]) -> {ok, {{simple_one_for_one, 0, 1}, [{?MODULE, {?MODULE, start_request, []}, temporary, brutal_kill, worker, [?MODULE]}]}}.
那么,当我们调用 supervisor:start_child/2 方法时,supervisor 内部会 调用 cowboy_requests_sup:start_request/5 方法。那么我们看下这个方法的代码:
-spec start_request(pid(), inet:socket(), module(), module(), any()) -> {ok, pid()}. start_request(ListenerPid, Socket, Transport, Protocol, Opts) -> Protocol:start_link(ListenerPid, Socket, Transport, Opts).
这个方法也是一行代码:
Protocol:start_link(ListenerPid, Socket, Transport, Opts).
= cowboy_http_protocol:start_link(ListenerPid, Socket, Transport, Opts).
我们看下 cowboy_http_protocol:start_link/4 方法,代码如下:
%% @doc Start an HTTP protocol process. -spec start_link(pid(), inet:socket(), module(), any()) -> {ok, pid()}. start_link(ListenerPid, Socket, Transport, Opts) -> Pid = spawn_link(?MODULE, init, [ListenerPid, Socket, Transport, Opts]), {ok, Pid}.
这边也是启动一个进程,并链接到调用进程。上面已经详细介绍了这个方法。我们跳到 cowboy_http_protocol:init/1 这个方法:
%% @private -spec init(pid(), inet:socket(), module(), any()) -> ok. init(ListenerPid, Socket, Transport, Opts) -> Dispatch = proplists:get_value(dispatch, Opts, []), MaxEmptyLines = proplists:get_value(max_empty_lines, Opts, 5), MaxKeepalive = proplists:get_value(max_keepalive, Opts, infinity), MaxLineLength = proplists:get_value(max_line_length, Opts, 4096), OnRequest = proplists:get_value(onrequest, Opts), OnResponse = proplists:get_value(onresponse, Opts), Timeout = proplists:get_value(timeout, Opts, 5000), URLDecDefault = {fun cowboy_http:urldecode/2, crash}, URLDec = proplists:get_value(urldecode, Opts, URLDecDefault), ok = cowboy:accept_ack(ListenerPid), wait_request(#state{listener=ListenerPid, socket=Socket, transport=Transport, dispatch=Dispatch, max_empty_lines=MaxEmptyLines, max_keepalive=MaxKeepalive, max_line_length=MaxLineLength, timeout=Timeout, onrequest=OnRequest, onresponse=OnResponse, urldecode=URLDec}).
前面 7行都是从 Opts 中读取配置,而 Opts = [{dispatch, Dispatch}],除了第一行,其他都用的默认配置,具体每个配置是什么意思,这里先不说,
从第 8行开始看:
URLDecDefault = {fun cowboy_http:urldecode/2, crash},
cowboy_http:urldecode/2 内容如下:
%% @doc Decode a URL encoded binary. %% The second argument specifies how to handle percent characters that are not %% followed by two valid hex characters. Use `skip' to ignore such errors, %% if `crash' is used the function will fail with the reason `badarg'. -spec urldecode(binary(), crash | skip) -> binary(). urldecode(Bin, OnError) when is_binary(Bin) -> urldecode(Bin, <<>>, OnError).
大概意思就是 对二进制进行URL解码。这个模块先不详细讲了,下一篇,会详细介绍。
URLDec = proplists:get_value(urldecode, Opts, URLDecDefault), 这一行也是从配置项中获取对应配置,不存在,则使用URLDecDefault;
这行,ok = cowboy:accept_ack(ListenerPid), 我们来简单看下:
%% @doc Acknowledge the accepted connection. %% %% Effectively used to make sure the socket control has been given to %% the protocol process before starting to use it. -spec accept_ack(pid()) -> ok. accept_ack(ListenerPid) -> receive {shoot, ListenerPid} -> ok end.
在这里读取消息,{shoot, ListenerPid},如果读取到,就继续往下走。那么时候究竟在什么地方会发送这个消息呢?
这边模块比较多,可能大家容易晕,建议对着代码看,就好了。
我们回到 cowboy_acceptor:acceptor/7 方法,我们继续往下看:
Transport:controlling_process(CSocket, Pid),
= cowboy_tcp_transport:controlling_process(CSocket, Pid),
具体代码如下:
%% @doc Assign a new controlling process <em>Pid</em> to <em>Socket</em>. %% @see gen_tcp:controlling_process/2 -spec controlling_process(inet:socket(), pid()) -> ok | {error, closed | not_owner | atom()}. controlling_process(Socket, Pid) -> gen_tcp:controlling_process(Socket, Pid).
这个是意思是:把一个套接字的控制进程改为新的控制进程NewPid,还是老规矩,给出 erlang doc 地址:http://www.erlang.org/doc/man/gen_tcp.html
controlling_process(Socket, Pid) -> ok | {error, Reason}
Assigns a new controlling process Pid to Socket. The controlling process is the process which receives messages from the socket. If called by any other process than the current controlling process, {error, eperm} is returned.
不知道为什么这里要这么做,先留下疑问吧,之后再去解决。
我们又要回到 cowboy_acceptor:acceptor/7 方法,看下:
cowboy_listener:add_connection(ListenerPid, default, Pid, OptsVsn);
我们看下这个方法的代码:
%% @doc Add a connection to the given pool in the listener. %% %% Pools of connections are used to restrict the maximum number of connections %% depending on their type. By default, Cowboy add all connections to the %% pool <em>default</em>. It also checks for the maximum number of connections %% in that pool before accepting again. This function only returns when there %% is free space in the pool. %% %% When a process managing a connection dies, the process is removed from the %% pool. If the socket has been sent to another process, it is up to the %% protocol code to inform the listener of the new <em>ConnPid</em> by removing %% the previous and adding the new one. %% %% This function also returns whether the protocol options have been modified. %% If so, then an {upgrade, ProtoOpts, OptsVsn} will be returned instead of %% the atom 'ok'. The acceptor can then continue with the new protocol options. -spec add_connection(pid(), atom(), pid(), non_neg_integer()) -> ok | {upgrade, any(), non_neg_integer()}. add_connection(ServerPid, Pool, ConnPid, OptsVsn) -> gen_server:call(ServerPid, {add_connection, Pool, ConnPid, OptsVsn}, infinity).
这个方法,也是一行代码,ServerPid 是cowboy_listener工作进程的进程标识;所以这里就是给这个工作进程发送一条异步消息,gen_server:call/3 不详细解释了,参看 Erlang OTP 设计原则。
当这个 cowboy_listener 工作进程收到这条异步消息时,又会做些什么呢,我们下一篇继续分析,谢谢大家。
本文基于署名-非商业性使用 3.0许可协议发布,欢迎转载,演绎,但是必须保留本文的署名rhinovirus(包含链接http://www.cnblogs.com/rhinovirus/),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系。