1. 简单介绍
ranch是一个使用erlang开发的socket acceptor pool for TCP protocols.
cowboy中使用ranch作为网络通信的基础
2. 使用
(1) 启动ranch应用
application:start(ranch).
(2) 侦听接收
ranch:start_listener(Ref, NbAcceptors,
Transport, TransOpts,
Protocol, ProtoOpts).
其中
Ref: 标识该侦听的唯一标识
NbAcceptors: acceptor进程的个数
Transport: 传输模块的名称, 通常为 ranch_tcp或ranch_ssl
TransOpts: 侦听的相关选项参数
默认的选项参数有 binary, {active, false}, {packet, raw}, {reuseaddr, true}
注意: 这几个默认的参数在调用start_listeners时不能更改, 但可以调用Transport:setopts/2进行修改
其他选项参数有 侦听端口:{port, Port}, 最大连接数: {max_connections, MaxConn}
Protocol: 回调协议解析模块
ProtoOpts: 回调协议解析模块的所需参数
例如
ranch:start_listener(client, 10,
ranch_tcp,
[{port, 80}, {max_connections, infinity}],
cowboy_protocol, []).
(3) 协议解析模块
该模块需要实现ranch_protocol行为, 即导出start_link/4函数. 该函数会由ranch_conns_sup进行回调, 该函数返回一个进程pid——{ok, Pid}.
回调start_link/4 传入的四个参数为 侦听的唯一标识Ref, 新连接的socket句柄Sock, 传输模块名称Transport以及协议解析模块的参数(ranch:start_listener中传入的).
在协议解析模块中, 通过调用传输模块提供的 recv/3 和 send/2 接口完成数据的收发工作。
3. 内部细节
(1) 监督树
ranch应用启动会默认创建ranch_sup进程和ranch_server进程, 当调用ranch:start_listerner后, 创建ranch_listener_sup, ranch_conns_sup, ranch_acceptor_sup等进程, 关系如上图所示. 多次调用start_listerner接口时, 会有多个ranch_listener_sup进程以及它的子进程.
(2) start_listener的流程
注意两个同步流程
1) ranch_acceptor进程调用start_protocol同步等待ranch_conns_sup进程回复, 用于控制最大连接数
2) 协议解析模块需调用ranch:accept_ack/1 同步等待ranch_conns_sup回复后, 才能真正进行数据的接收。
ranch_conns_sup.erl
start_protocol(SupPid, Socket) ->
SupPid ! {?MODULE, start_protocol, self(), Socket},
receive
SupPid ->
ok
end.
loop(...)
receive
{?MODULE, start_protocol, To, Socket} ->
case Protocol:start_link(Ref, Socket, Transport, Opts) of
{ok, Pid} ->
Transport:controlling_process(Socket,Pid),
Pid ! {shoot, Ref},
put(Pid, true),
CurConns2 = CurConns + 1,
if CurCons2 < MaxConns ->
To ! self(),
loop(...);
true ->
loop(...)
end;
_ ->
To ! self(),
Transport:close(Socket),
loop(...)
end;
...
ranch.erl
accept_ack(Ref) ->
receive
{shoot, Ref} ->
ok
end.
(3) ranch_server进程
该进程的作用是记录通过调用start_listerner侦听的相关信息, 包括
侦听端口: ets:insert(?TAB, {{port, Ref}, Port})
最大连接数: ets:insert(?TAB, {{max_conns, Ref}, MaxConns})
协议解析参数: ets:insert(?TAB, {{opts, Ref}, Opts})
连接池监督者进程: ets:insert(?TAB, {{conns_sup, Ref}, Pid})
记录这些信息的目的是为了动态进行设置.
ranch_server:set_max_connections(Ref, MaxConnections)
ranch_server:set_new_listener_opts(Ref, MaxConnections, Opts)
ranch_server:set_protocol_optsions(Ref, ProtoOpts)
通过这些接口可进行动态设置, 这几接口的实现都是向ranch_server进程投递消息, ranch_server收到消息后更新ets表中的数据, 并根据ref找到对应的连接池监督者进程, 并消息通知最新的配置信息.