ranch 下载网址:https://github.com/ninenines/ranch
一、ranch是什么?
一个支持多协议进行网络通信的路由池,非常便于嵌入应用,但只能开启一个端口。
二、ranch的特点
1、ranch 提供模块化的设计,允许你在某个监听器中选择具体的传输协议(ranch 允许多个应用同时使用)。
2、监听器在一个端口上接收和管理连接,并且包含限制并发连接数的实用功能。
3、所有连接均以有序形式保存在 connection pool 中,每一个 pool 拥有独立的配置约束。
4、ranch可以不需要关闭打开连接的情况下更新池配置。
三、ranch使用
启动ranch应用application:start(ranch),创建一个ranch_sup根监督者,ranch_sup 启动一个子工作进程ranch_server。
ranch_server 是基于 gen_server 实现,用来维护配置信息,底层存储使用 ets。
应用可调用ranch:start_listener()开启一个监听,在此函数中会调用supervisor:start_child动态添加子监督者进程ranch_listener_sup, ranch_listener_sup作为ranch_conns_sup和ranch_acceptors_sup的监督者。
ranch_conns_sup 和ranch_acceptors_sup是ranch_listener_sup启动的子进程。
如下面代码:
子进程的启动是按照列表顺序从左到右启动,也就是先启动 ranch_conns_sup,再启动 ranch_acceptors_sup。
ranch_acceptors_sup会启动N个acceptor进程。这N个acceptor进程用于接收外部的连接。
ranch_conns_sup 用来管理外部连接的数量,最大的外部并发连接数是maxconns+NAcceptor -1, 默认maxconns的值为1024。
四、ranch进程流程
当外部连接ranch应用的监听端口时,ranch_acceptor接收外部连接,此时进入ranch_acceptor循环,在此循环中调用start_protocol函数向ranch_conns_sup发送start_protocol消息,进入ranch_conns_sup循环,在ranch_conns_sup循环中,启动Protocol:start_link创建业务进程。并把Socket的控制权给此进程,然后比较当前外部连接数和maxConns的大小,若当前连接数小于maxConns的数不成立,此acceptor进入休眠状态。当有连接进程退出时,就会减少当前连接数,从而唤起睡眠的acceptor进程。
五、ranch 最大并发连接数
NAcceptor : 是创建的acceptor的数量,启动ranch:start_listener()时创建,创建成功后acceptor的数量不再改变,通过调用ranch:set_max_connections()动态设置MaxConns的参数。
由于在ranch_conns_sup.erl代码中的loop循环中是先把socket的控制权给xxx_protocol,
再比较CurConns2=Curconns+1 <MaxConns是否成立,即当acceptor可以接收外部连接时,连接就会成功,直到所有的acceptor都阻塞外部连接此时就不再成功。故ranch 最大并发连接数应该是:NAcceptor + MaxConns – 1
例如: 假设:MaxConns= 2,NAcceptor=2
第一个连接来的时候,此时连接数为1 CurConns2=0+1=1 < 2 没有acceptor阻塞
第二个连接来的时候, 此时连接数为2 CurConns2=1+1=2 < 2 不成立, 阻塞一个acceptor
第三个连接来的时候,此时连接数为3 Curconns2= 2+1=3<2 不成立,acceptor全被阻塞
第四个连接来的时候,此时如若没有连接退出,就不能再接收外部的连接。
故 ranch最大并发连接数为:3 即 NAcceptor+MaxConns-1 = 2+2-1
ranch_conns_sup中循环的相关代码如下:
六、ranch_protocol中自定义行为模式的实现
自定义行为模块中需要实现:ranch_protocol模块中的start_link函数。
在自定义行为模式中:init回调函数中调用ranch:accept_ack来得到socket的控制权。
ranch_tcp默认使用{active, false}配置,{active, false}选项用被动模式打开套接字。这样服务器不会因为某个过激的客户端试图用过量的数据冲击它而崩溃。
服务器循环里的代码 会在每次想要接收数据时调用gen_tcp:recv, ranch_tcp对gen_tcp进行了封装Transport:recv(Socket, Length, Timeout )。客户端一直被阻塞,直到服务器调用recv为止。
Transpost:recv中的length参数
%% The Length argument is only meaningful when the socket
%% is in raw mode and denotes the number of bytes to read.
%% If Length = 0, all available bytes are returned.
%% If Length > 0, exactly Length bytes are returned, or an error;
%% possibly discarding less than Length bytes of data
%% when the socket gets closed from the other side.
例子:
-module(echo_protocol). -behaviour(ranch_protocol). -export([start_link/4]). -export([init/4]). start_link(Ref, Socket, Transport, Opts) -> Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]), {ok, Pid}. init(Ref, Socket, Transport, _Opts = []) -> %% 必须调用 ranch:accept_ack,确保socket控制权 ok = ranch:accept_ack(Ref), loop(Socket, Transport). loop(Socket, Transport) -> case Transport:recv(Socket, 0, 5000) of {ok, Data} -> Transport:send(Socket, Data), loop(Socket, Transport); _ -> ok = Transport:close(Socket) end.
七、ranch外部接口
启动ranch应用使用application:start(ranch).
start_listener()开启一个ranch监听。
如在cowboy代码中的调用:
ranch:start_listener(Ref, NbAcceptors, ranch_tcp, TransOpts, cowboy_protocol, ProtoOpts).
stop_listener()关闭ranch监听。
child_spec() 生成适合嵌入应用的子进程规范。
accept_ack() 得到socket的控制权,用来消息传递的同步,防止工作进程提前执行到recv代码。
remove_connection(): 移除一个连接
系统中可能存在一些长期存活的连接,但是这些连接大部分时候并没事可做,可能在等待客户端的数据
这个时候,工作进程就应该调用这个函数,将当前连接数减1.
因为这个连接在限制并发连接数时不应该被统计在内,这个设计还是很精妙的
get_port()查询listen socket的监听端口
get_max_connections() 查询设置的maxConns
set_max_connections(ref(), max_conns()) 设置maxConns
%% ranch:set_max_connections --> ranch_server:set_max_connections --> 给 conns sup 进程发消息,{set_max_conns, MaxConns}
%% ranch 模块提供 API,给外部应用使用; ranch_server 保存所有应用的配置信息;conns sup 进程才是控制连接并发数的最终进程