【Cowboy源码阅读-2】Http服务器的启动流程

it2025-04-23  26

通过 examples中的 echo_get_app.erl 来顺着捋下来整体流程

echo_get_app.erl中的start函数,Dispatch是一个路由逻辑,暂时先不细究,先看 cowboy:start_clear/3,调用cowboy:start_clear/3会开启一个 listener 的监督者

cowboy:start_clear/3会先对参数进行格式化处理(List转Map等),其中比较有趣的就是ranch:normalize_opts/1中会处理 ack_timeout 的时间,读到这里产生一种莫名的底层牛逼感。参数格式化完成之后就会调用ranch:start_listene/5,ranch:start_listene/5在普通情况下的主要功能就是让ranch_sup去启动ranch_listener_sup


ranch_listener_sup:start_link/5 作用又分成: ranch_server:set_new_listener_opts/5 这个函数的作用就是向ranch_server的gen_server中设置参数 由 ranch_sup 启动 ranch_server 其中各个参数分别是: Ref : http Transport : ranch_tcp, MaxConns : 1024, TransOpts : #{connection_type => supervisor,socket_opts => [{port,8080}]}, Protocol : cowboy_clear, ProtoOpts : #{connection_type => supervisor, env => #{dispatch => [{’_’,[],[{[],[],toppage_h,[]}]}]}} ranch_server:set_listener_sup(Ref, self()),将监听的监督者设置成自己,后续会用到 开启ranch_conns_sup,并且把自己set到ranch_server里面,ranch_server:set_connections_sup(Ref, self()) 。 开启ranch_acceptors_sup ,ranch_acceptors_sup的操作其实就是创建一个listener_socket,然后开启 ranch_acceptor 这个worker 进程。

注意这里的细节,是先开启 负责处理connect_socket的监督者 (connect_socket 和 listener_socket需要分清楚)


ranch_acceptor会在loop里面循环的accept,accept到connect_socket之后,就把socket controlling_process 给 ranch_conns_sup,然后发送消息 {?MODULE, start_protocol, self(), Socket} 给 ranch_conns_sup ,并且用 receive阻塞自己。(看到这里可能有点奇怪,怎么没有超时时间的?其实后面在处理连接的时候,如果超过最大连接数量,这里就一直阻塞了)

顺着下去看 ranch_conns_sup:loop/4,收到 {?MODULE, start_protocol, self(), Socket} 后,会调用cowboy_clear:start_link/4(Protocol就是cowboy_clear),cowboy_clear:start_link/4会阻塞在ranch:handshake/1这一操作,只有收到指定消息后,才去初始化初始化 cowboy_http 或者 cowboy_http2(原因参考问题1)!初始化完成回过头继续看ranch_conns_sup:loop/4,会调用handshake/8。

handshake(State=#state{ref=Ref, transport=Transport, handshake_timeout=HandshakeTimeout, max_conns=MaxConns}, CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid) -> %%第一句,就是把 socket 再 controlling_process 给 cowboy_http 所在的进程 case Transport:controlling_process(Socket, ProtocolPid) of ok -> %%这一句很妙! ProtocolPid ! {handshake, Ref, Transport, Socket, HandshakeTimeout}, put(SupPid, active), CurConns2 = CurConns + 1, if CurConns2 < MaxConns -> %%还记得 ranch_acceptor 的阻塞吗? %%如果连接数量没超过最大数量,就直接通知 ranch_acceptor 继续 acceptor下一个 To ! self(), loop(State, CurConns2, NbChildren + 1, Sleepers); true -> %%达到最大连接数量了,让ranch_acceptor先阻塞着,并且添加到 Sleepers 里面 %%后续收到 {'EXIT', Pid, Reason} 的时候,会对 Sleepers 进行处理 loop(State, CurConns2, NbChildren + 1, [To|Sleepers]) end; {error, _} -> Transport:close(Socket), %% Only kill the supervised pid, because the connection's pid, %% when different, is supposed to be sitting under it and linked. exit(SupPid, kill), To ! self(), loop(State, CurConns, NbChildren, Sleepers) end.

问题: 这些步骤下来,让我不明白的是,Socket被acceper出来之后经过一系列操作之后才会绑定到 cowboy_http 所在的进程,期间Socket收到的消息是怎么处理的??不会丢失吗? 答:找到答案了,就是Socket创建的时候{active,false},到子进程之后再转成true,可以参考cowboy_http:setopts_active/1。 http://www.voidcn.com/article/p-hgahacrt-bvb.html

提一些代码细节: erlang:function_exported(Transport,name,0),检查函数使用有导出,这里检查的是 ranch_tcp:name/0 经过查阅,proc_lib:init_ack是用来返回 proc_lib:start_link 的 (ok = proc_lib:init_ack(Parent, {ok, self()}),)

最新回复(0)