前言
正常情况下, 当我们需要连接外部资源的时候, 有两种方法, 一种是 http 请求的方法, 另一种则是 socket 请求的方法. http 请求对应的是 cowboy, 而 socket 请求对应的则是 ranch. socket 请求比较常见的是传输银行规则里的 8583 报文.
创建项目
- 使用 rebar3 new release 的方法创建一个新的 otp 项目.
- 在 .app 文件启动 ranch 监听
{ok, _Pid} = ranch:start_listener(service_tcp, Acceptor, ranch_tcp,
[
{port, Port},
{max_connections, Max_connect}
],
service_tcp_protocol, []);
上面代码中, service_tcp 是连接的名称, Acceptor 代表接收器的数量, port代表向外开放的端口, service_tcp_protocol 代表处理模块的名字. 另外, 可以将 Acceptor 放到列表中, 格式为 {num_acceptors, Acceptor}, 这两种写法ranch都支持.
- 如果要使用 ssl 格式而非 tcp 格式, 则需要几个额外参数,
{certfile, CertFile},
{cacertfile, CaCertFile},
{keyfile, KeyFile}
以上参数的获取方式可以参考 https://ninenines.eu/docs/en/ranch/1.7/guide/ssl_auth/
- 处理模块可以使用 gen_server 的模式.
start_link(Ref, Socket, Transport, Opts) ->
proc_lib:start_link(?MODULE, init, [Ref, Socket, Transport, Opts]).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
process_flag(trap_exit, true),
{ok, undefined}.
init(Ref, Socket, Transport, _Opts = []) ->
ok = proc_lib:init_ack({ok, self()}),
ok = ranch:accept_ack(Ref),
ok = Transport:setopts(Socket, [{active, once}, {packet, 0}]),
gen_server:enter_loop(?MODULE, [], #state{socket = Socket, transport = Transport}, TIMEOUT).
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(_Request, State) ->
{noreply, State}.
handle_info({tcp, Socket, Data}, State = #state{socket = Socket, transport = TransPort}) ->
Response = Data,
TransPort:setopts(Socket, [{active, once}]),
TransPort:send(Socket, Response),
{noreply, State, 0};
handle_info({tcp_closed, _Socket}, State) ->
{stop, normal, State};
handle_info({tcp_error, _, Reason}, State) ->
{stop, Reason, State};
handle_info(timeout, State) ->
{stop, normal, State};
handle_info(Info, State) ->
{stop, normal, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.