ranch

整体理解

从整体上的话,ranch主要是三层的监控树

  • 第一层 ranch_sup,负责整个应用的启动,启动了ranch_server进程,它管理了整个应用的配置和连接数据
  • 第二层 ranch_listener_sup,负责连接的管理
  • 第三层 ranch_conns_sup和ranch_acceptors_sup,这两个分别用来处理新的连接和获得新的连接

    当然最底层的ranch_acceptor是应用中的重要角色,每当有新的连接,都会把控制权交给ranch_conns_sup,由它统一管理

ranch.app

启动模块为ranch_app,说明需要找到ranch_app.erl文件去启动应用

ranch_app.erl

根据参数启动测试的功能,主要启动了一个ranch_sup监控进程

ranch_sup.erl

新建一个名为ranch_server的ets表,同时启动并监控ranch_server进程,策略为one_for_one

ranch_server.erl

启动了一个进程,管理ranch_server这个ets表,提供多个接口

set_new_listener_opts:设置监听进程的参数

set_connections_sup:增加新的连接进程的监控进程Pid,并且对该进程进行monitor监视,把{MonitorRef, Pid}添加到#state.monitors中

set_listener_sup:增加一个监听进程的监控进程Pid,并且对该进程进行monitor监视,并且把{MonitorRef, Pid}添加到#state.monitors中

set_addr:在ets中记录地址

set_max_conns:设置最大连接数量

set_trans_opts:设置传输协议参数

set_proto_opts:设置协议参数

到此为止,ranch应用的准备工作已经结束,剩下的就差外部的调用了


ranch.erl

ranch应用的调用模块,通过start_listener/6来初始化ranch的功能模块,给它提供功能参数,其中有一个Transport参数,是ranch的协议模块名,要么是ranch_ssl,要么就是ranch_tcp,先在ranch_sup下面启动了一个ranch_listener_sup进程,该进程做了什么,接下来将详细介绍,至少在这里我们知道,ranch的正式使用由ranch_listener_sup进程启动开始。

  1. -spec start_listener(ref(), module(), any(), module(), any())
  2. -> supervisor:startchild_ret().
  3. start_listener(Ref, Transport, TransOpts, Protocol, ProtoOpts) ->
  4. NumAcceptors = proplists:get_value(num_acceptors, TransOpts, 10),
  5. start_listener(Ref, NumAcceptors, Transport, TransOpts, Protocol, ProtoOpts).
  6. -spec start_listener(ref(), non_neg_integer(), module(), any(), module(), any())
  7. -> supervisor:startchild_ret().
  8. start_listener(Ref, NumAcceptors, Transport, TransOpts, Protocol, ProtoOpts)
  9. when is_integer(NumAcceptors) andalso is_atom(Transport)
  10. andalso is_atom(Protocol) ->
  11. _ = code:ensure_loaded(Transport),
  12. case erlang:function_exported(Transport, name, 0) of
  13. false ->
  14. {error, badarg};
  15. true ->
  16. Res = supervisor:start_child(ranch_sup, child_spec(Ref, NumAcceptors,
  17. Transport, TransOpts, Protocol, ProtoOpts)),
  18. Socket = proplists:get_value(socket, TransOpts),
  19. case Res of
  20. {ok, Pid} when Socket =/= undefined ->
  21. %% Give ownership of the socket to ranch_acceptors_sup
  22. %% to make sure the socket stays open as long as the
  23. %% listener is alive. If the socket closes however there
  24. %% will be no way to recover because we don't know how
  25. %% to open it again.
  26. Children = supervisor:which_children(Pid),
  27. {_, AcceptorsSup, _, _}
  28. = lists:keyfind(ranch_acceptors_sup, 1, Children),
  29. %%% Note: the catch is here because SSL crashes when you change
  30. %%% the controlling process of a listen socket because of a bug.
  31. %%% The bug will be fixed in R16.
  32. catch Transport:controlling_process(Socket, AcceptorsSup);
  33. _ ->
  34. ok
  35. end,
  36. maybe_started(Res)
  37. end.
  1. -spec child_spec(ref(), module(), any(), module(), any())
  2. -> supervisor:child_spec().
  3. child_spec(Ref, Transport, TransOpts, Protocol, ProtoOpts) ->
  4. NumAcceptors = proplists:get_value(num_acceptors, TransOpts, 10),
  5. child_spec(Ref, NumAcceptors, Transport, TransOpts, Protocol, ProtoOpts).
  6. -spec child_spec(ref(), non_neg_integer(), module(), any(), module(), any())
  7. -> supervisor:child_spec().
  8. child_spec(Ref, NumAcceptors, Transport, TransOpts, Protocol, ProtoOpts)
  9. when is_integer(NumAcceptors) andalso is_atom(Transport)
  10. andalso is_atom(Protocol) ->
  11. {{ranch_listener_sup, Ref}, {ranch_listener_sup, start_link, [
  12. Ref, NumAcceptors, Transport, TransOpts, Protocol, ProtoOpts
  13. ]}, permanent, infinity, supervisor, [ranch_listener_sup]}.

ranch_listener_sup.erl

该监控进程启动时,主动调用ranch_server:set_listener_sup/2,将自己的信息记录在ets中并且被ranch_server监控,它下面还顺序启动了ranch_conns_sup和ranch_acceptors_sup,策略是rest_for_one,因为ranch_conns_sup是负责监控连接的进程,而ranch_acceptors_sup是监控消息的进程,ranch_conns_sup死掉之后,说明连接都断开了,ranch_acceptors_sup下面的进程也就无法运行,必须等ranch_conns_sup重启成功后才能正常工作。

ranch_conns_sup.erl

该模块并不是supervisor行为,不过作者手动写了一个类似supervisor的东西,启动时主动调用ranch_server:set_connections_sup/2记录自身的信息,同时通过ranch_server获取相应的一些连接参数,其中用到了proc_lib:init_ack/2用于响应proc_lib:start_link/3,实现同步启动进程,做到和gen_server一样的效果,接着开始一个循环函数loop/4,用来处理消息,下面列出主要的消息处理

{?MODULE, start_protocol, T, Socket}:参数中To为ranch_acceptor模块的进程pid,而Socket是ranch_acceptor接收到的客户端socket,启动一个调用Protocol:start_link/4启动一个进程,这个Protocol是用户实现的回调模块,通常是socket消息的接收处理进程,就像例子中的echo_protocol.erl或者reverse_protocol.erl这两个部分,如果启动成功,将会调用shoot/8来修改回调模块的Socket的控制进程,即socket的消息将发送到哪个进程在这里决定,修改之后,将回复回调部分进程一个{shoot, Ref, Transport, Socket, AckTimeout}消息,接着检查当前连接数量是否达到配置中的MaxConns,如果达到了最大连接数的话则把连接加入到等待连接列表中,同时增加子连接数量,继续循环loop/4

{?MODULE, active_connections, To, Tag}:To连接进程获取当前连接列表

{remove_connection, Ref, Pid}:移除某个连接进程

  1. -spec init(pid(), ranch:ref(), module(), module()) -> no_return().
  2. init(Parent, Ref, Transport, Protocol) ->
  3. process_flag(trap_exit, true),
  4. ok = ranch_server:set_connections_sup(Ref, self()),
  5. MaxConns = ranch_server:get_max_connections(Ref),
  6. TransOpts = ranch_server:get_transport_options(Ref),
  7. ConnType = proplists:get_value(connection_type, TransOpts, worker),
  8. Shutdown = proplists:get_value(shutdown, TransOpts, 5000),
  9. AckTimeout = proplists:get_value(ack_timeout, TransOpts, 5000),
  10. ProtoOpts = ranch_server:get_protocol_options(Ref),
  11. ok = proc_lib:init_ack(Parent, {ok, self()}),
  12. loop(#state{parent=Parent, ref=Ref, conn_type=ConnType,
  13. shutdown=Shutdown, transport=Transport, protocol=Protocol,
  14. opts=ProtoOpts, ack_timeout=AckTimeout, max_conns=MaxConns}, 0, 0, []).
  1. loop(State=#state{parent=Parent, ref=Ref, conn_type=ConnType,
  2. transport=Transport, protocol=Protocol, opts=Opts,
  3. max_conns=MaxConns}, CurConns, NbChildren, Sleepers) ->
  4. receive
  5. {?MODULE, start_protocol, To, Socket} ->
  6. try Protocol:start_link(Ref, Socket, Transport, Opts) of
  7. {ok, Pid} ->
  8. shoot(State, CurConns, NbChildren, Sleepers, To, Socket, Pid, Pid);
  9. {ok, SupPid, ProtocolPid} when ConnType =:= supervisor ->
  10. shoot(State, CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid);
  11. Ret ->
  12. To ! self(),
  13. error_logger:error_msg(
  14. "Ranch listener ~p connection process start failure; "
  15. "~p:start_link/4 returned: ~999999p~n",
  16. [Ref, Protocol, Ret]),
  17. Transport:close(Socket),
  18. loop(State, CurConns, NbChildren, Sleepers)
  19. catch Class:Reason ->
  20. To ! self(),
  21. error_logger:error_msg(
  22. "Ranch listener ~p connection process start failure; "
  23. "~p:start_link/4 crashed with reason: ~p:~999999p~n",
  24. [Ref, Protocol, Class, Reason]),
  25. loop(State, CurConns, NbChildren, Sleepers)
  26. end;
  27. {?MODULE, active_connections, To, Tag} ->
  28. To ! {Tag, CurConns},
  29. loop(State, CurConns, NbChildren, Sleepers);
  30. %% Remove a connection from the count of connections.
  31. {remove_connection, Ref, Pid} ->
  32. case put(Pid, removed) of
  33. active ->
  34. loop(State, CurConns - 1, NbChildren, Sleepers);
  35. remove ->
  36. loop(State, CurConns, NbChildren, Sleepers);
  37. undefined ->
  38. _ = erase(Pid),
  39. loop(State, CurConns, NbChildren, Sleepers)
  40. end;
  41. %% Upgrade the max number of connections allowed concurrently.
  42. %% We resume all sleeping acceptors if this number increases.
  43. {set_max_conns, MaxConns2} when MaxConns2 > MaxConns ->
  44. _ = [To ! self() || To <- Sleepers],
  45. loop(State#state{max_conns=MaxConns2},
  46. CurConns, NbChildren, []);
  47. {set_max_conns, MaxConns2} ->
  48. loop(State#state{max_conns=MaxConns2},
  49. CurConns, NbChildren, Sleepers);
  50. %% Upgrade the protocol options.
  51. {set_opts, Opts2} ->
  52. loop(State#state{opts=Opts2},
  53. CurConns, NbChildren, Sleepers);
  54. {'EXIT', Parent, Reason} ->
  55. terminate(State, Reason, NbChildren);
  56. {'EXIT', Pid, Reason} when Sleepers =:= [] ->
  57. case erase(Pid) of
  58. active ->
  59. report_error(Ref, Protocol, Pid, Reason),
  60. loop(State, CurConns - 1, NbChildren - 1, Sleepers);
  61. removed ->
  62. report_error(Ref, Protocol, Pid, Reason),
  63. loop(State, CurConns, NbChildren - 1, Sleepers);
  64. undefined ->
  65. loop(State, CurConns, NbChildren, Sleepers)
  66. end;
  67. %% Resume a sleeping acceptor if needed.
  68. {'EXIT', Pid, Reason} ->
  69. case erase(Pid) of
  70. active when CurConns > MaxConns ->
  71. report_error(Ref, Protocol, Pid, Reason),
  72. loop(State, CurConns - 1, NbChildren - 1, Sleepers);
  73. active ->
  74. report_error(Ref, Protocol, Pid, Reason),
  75. [To|Sleepers2] = Sleepers,
  76. To ! self(),
  77. loop(State, CurConns - 1, NbChildren - 1, Sleepers2);
  78. removed ->
  79. report_error(Ref, Protocol, Pid, Reason),
  80. loop(State, CurConns, NbChildren - 1, Sleepers);
  81. undefined ->
  82. loop(State, CurConns, NbChildren, Sleepers)
  83. end;
  84. {system, From, Request} ->
  85. sys:handle_system_msg(Request, From, Parent, ?MODULE, [],
  86. {State, CurConns, NbChildren, Sleepers});
  87. %% Calls from the supervisor module.
  88. {'$gen_call', {To, Tag}, which_children} ->
  89. Children = [{Protocol, Pid, ConnType, [Protocol]}
  90. || {Pid, Type} <- get(),
  91. Type =:= active orelse Type =:= removed],
  92. To ! {Tag, Children},
  93. loop(State, CurConns, NbChildren, Sleepers);
  94. {'$gen_call', {To, Tag}, count_children} ->
  95. Counts = case ConnType of
  96. worker -> [{supervisors, 0}, {workers, NbChildren}];
  97. supervisor -> [{supervisors, NbChildren}, {workers, 0}]
  98. end,
  99. Counts2 = [{specs, 1}, {active, NbChildren}|Counts],
  100. To ! {Tag, Counts2},
  101. loop(State, CurConns, NbChildren, Sleepers);
  102. {'$gen_call', {To, Tag}, _} ->
  103. To ! {Tag, {error, ?MODULE}},
  104. loop(State, CurConns, NbChildren, Sleepers);
  105. Msg ->
  106. error_logger:error_msg(
  107. "Ranch listener ~p received unexpected message ~p~n",
  108. [Ref, Msg]),
  109. loop(State, CurConns, NbChildren, Sleepers)
  110. end.
  1. shoot(State=#state{ref=Ref, transport=Transport, ack_timeout=AckTimeout, max_conns=MaxConns},
  2. CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid) ->
  3. case Transport:controlling_process(Socket, ProtocolPid) of
  4. ok ->
  5. ProtocolPid ! {shoot, Ref, Transport, Socket, AckTimeout},
  6. put(SupPid, active),
  7. CurConns2 = CurConns + 1,
  8. if CurConns2 < MaxConns ->
  9. To ! self(),
  10. loop(State, CurConns2, NbChildren + 1, Sleepers);
  11. true ->
  12. loop(State, CurConns2, NbChildren + 1, [To|Sleepers])
  13. end;
  14. {error, _} ->
  15. Transport:close(Socket),
  16. %% Only kill the supervised pid, because the connection's pid,
  17. %% when different, is supposed to be sitting under it and linked.
  18. exit(SupPid, kill),
  19. To ! self(),
  20. loop(State, CurConns, NbChildren, Sleepers)
  21. end.

ranch_acceptors_sup.erl

从ranch_server中获取ranch_conns_sup的进程,并且获取监听参数TransOpts,如果ranch_server中尚未有监听socket,则启动监听socket,接着把监听socket记录到ranch_server中,启动一个ranch_acceptor子进程。

ranch_acceptor.erl

启动一个loop/3循环,当接收到客户端的socket之后,把socket的控制进程改为连接监控进程ranch_conns_sup,连接监控进程中有对应的一些消息处理,接着调用ranch_conns_sup:start_protocol/2发送{?MODULE, start_protocol, self(), Socket},ranch_conns_sup进程自身对该消息进行处理,详情看ranch_conns_sup.erl的介绍,至此,ranch的监听端口的工作都已经准备完毕,(发现还有部分忽略了,需要实现ranch_protocol行为才能处理客户端消息的)现在就差客户端的连接进来了。

  1. -spec loop(inet:socket(), module(), pid()) -> no_return().
  2. loop(LSocket, Transport, ConnsSup) ->
  3. _ = case Transport:accept(LSocket, infinity) of
  4. {ok, CSocket} ->
  5. case Transport:controlling_process(CSocket, ConnsSup) of
  6. ok ->
  7. %% This call will not return until process has been started
  8. %% AND we are below the maximum number of connections.
  9. ranch_conns_sup:start_protocol(ConnsSup, CSocket);
  10. {error, _} ->
  11. Transport:close(CSocket)
  12. end;
  13. %% Reduce the accept rate if we run out of file descriptors.
  14. %% We can't accept anymore anyway, so we might as well wait
  15. %% a little for the situation to resolve itself.
  16. {error, emfile} ->
  17. error_logger:warning_msg("Ranch acceptor reducing accept rate: out of file descriptors~n"),
  18. receive after 100 -> ok end;
  19. %% We want to crash if the listening socket got closed.
  20. {error, Reason} when Reason =/= closed ->
  21. ok
  22. end,
  23. flush(),
  24. ?MODULE:loop(LSocket, Transport, ConnsSup).

ranch源码阅读的更多相关文章

  1. 【原】FMDB源码阅读(三)

    [原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...

  2. 【原】FMDB源码阅读(二)

    [原]FMDB源码阅读(二) 本文转载请注明出处 -- polobymulberry-博客园 1. 前言 上一篇只是简单地过了一下FMDB一个简单例子的基本流程,并没有涉及到FMDB的所有方方面面,比 ...

  3. 【原】FMDB源码阅读(一)

    [原]FMDB源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 说实话,之前的SDWebImage和AFNetworking这两个组件我还是使用过的,但是对于 ...

  4. 【原】AFNetworking源码阅读(六)

    [原]AFNetworking源码阅读(六) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AF ...

  5. 【原】AFNetworking源码阅读(五)

    [原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...

  6. 【原】AFNetworking源码阅读(四)

    [原]AFNetworking源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇还遗留了很多问题,包括AFURLSessionManagerTaskDe ...

  7. 【原】AFNetworking源码阅读(三)

    [原]AFNetworking源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇的话,主要是讲了如何通过构建一个request来生成一个data tas ...

  8. 【原】AFNetworking源码阅读(二)

    [原]AFNetworking源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中我们在iOS Example代码中提到了AFHTTPSessionMa ...

  9. 【原】AFNetworking源码阅读(一)

    [原]AFNetworking源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 AFNetworking版本:3.0.4 由于我平常并没有经常使用AFNetw ...

随机推荐

  1. 课时5:POW,POS,DPOS(矿工/矿机,工作证明)

    比特币钱包

  2. 用go iris 写的一个网页版文件共享应用(webapp)

    主要演示文件拖拽上传或点击上传到不同的目录中,提供下载和删除功能. 目录结构: -main.go --share(用于分类存放上传文件的目录) --v(视图目录) ---share.html main ...

  3. Java集合框架中底层文档的List与Set

    Java集合框架中的List与Set // 简书作者:达叔小生 Collection -> Set 无序不重复 -> 无序HashSet,需要排序TreeSet -> List 有序 ...

  4. Spring——代理实现AOP

    一.静态代理实现 1.接口(抽象主题) 2.接口的实现类(真实主题) 3.代理类(代理主题) 4.测试类: ApplicationContext context=new ClassPathXmlApp ...

  5. Vue_(组件)自定义指令

    Vue.js自定义指令 传送门 自定义指令:除了内置指令,Vue也允许用户自定义指令 注册指令:通过全局API Vue.directive可以注册自定义指令 自定义指令的钩子函数参数:自定义指令的钩子 ...

  6. Mac平台最好用的万能开源免费播放器-IINA

    1.安装 1)官网下载地址 https://iina.io/ 2)brew 方式安装 testdeMacBook-Pro:~ test$ brew cask install iina Updating ...

  7. 编译openwrt时报错build_dir/hostpkg/libubox-2018-07-25-c83a84af/blobmsg_json.c:21:19: fatal error: json.h: No such file or directory

    答: 一. 详细日志: build_dir/hostpkg/libubox-2018-07-25-c83a84af/blobmsg_json.c:21:19: fatal error: json.h: ...

  8. nodejs本版问题

    接到一个新项目,前端用的是VUE,这就需要安装nodejs.记得之前安装的是V6.X,在npm install加载依赖库node_modules的时候ant-design-vue总是报错.看到同事用n ...

  9. [SPSS]如何利用spss进行单侧检验

    根据网上经验来看,结论如下: 单双侧t检验,t值不变,p值除以2即为单侧p值.

  10. 六十五:CSRF攻击与防御之CSRF防御之form表单防御

    防御原理,将csrf_token放到session中,再将session放到cookie中 实现: from flask_wtf import CSRFProtect # flask_wtf 已经提供 ...