EMQX源码分析---esockd源码分析

esockd是一个Erlang异步非阻塞TCP/SSLSocket服务器框架,支持Acceptor池与异步Accept,UDP/DTLS服务器,最大连接数管理,动态IP否定和允许,客户端限速及IPv6支持。本文详细介绍其核心API,架构与源码。
摘要由CSDN通过智能技术生成

一、基本功能介绍

  1、esockd是erlang异步非阻塞TCP/SSL Socket服务器框架

  2、 支持Acceptor池与异步Accept

  3、支持UDP/DTLS 服务器

  4、支持最大连接数管理

  5、支持动态对指定的ip进行否定和允许

  6、客户端限速支持

  7、ip6支持

二、架构图

三、从esockd.erl 文件开始

该模块包含了核心API ,管理API,工具函数定义,类型定义等功能 

esockd源码如下:

%% Copyright (c) 2018 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.

-module(esockd).

-include("esockd.hrl").

-export([start/0]).

%% Core API
-export([open/4, open_udp/4, open_dtls/4, close/2, close/1]).
-export([reopen/1, reopen/2]).
-export([child_spec/4, udp_child_spec/4, dtls_child_spec/4]).

%% Management API
-export([listeners/0, listener/1]).
-export([get_stats/1, get_options/1, get_acceptors/1]).
-export([get_max_connections/1, set_max_connections/2, get_current_connections/1]).
-export([get_shutdown_count/1]).

%% Allow, Deny API
-export([get_access_rules/1, allow/2, deny/2]).

%% Utility functions
-export([parse_opt/1, ulimit/0, fixaddr/1, to_string/1]).


-type(proto() :: atom()).
%% transport()定义为module类型
-type(transport() :: module()).
%%
-type(udp_transport() :: {udp | dtls, pid(), inet:socket()}).
%% sock() 定义为 esockd_transport:sock()
-type(sock() :: esockd_transport:sock()).

-type(mfargs() :: atom() | {atom(), atom()} | {module(), atom(), [term()]}).

-type(sock_fun() :: fun((esockd_transport:sock()) -> {ok, esockd_transport:sock()} | {error, term()})).

-type(option() :: {acceptors, pos_integer()}|{max_connections, pos_integer()}
                | {max_conn_rate, pos_integer() | {pos_integer(), pos_integer()}}
                | {access_rules, [esockd_access:rule()]}|{shutdown, brutal_kill | infinity | pos_integer()}
                | tune_buffer | {tune_buffer, boolean()}| proxy_protocol | {proxy_protocol, boolean()}
                | {proxy_protocol_timeout, timeout()}| {ssl_options, [ssl:ssl_option()]}
                | {tcp_options, [gen_tcp:listen_option()]}| {udp_options, [gen_udp:option()]}
                | {dtls_options, [gen_udp:option() | ssl:ssl_option()]}).

%% 定义host类型,可能类型是inet:ip_address() 或者 string()
-type(host() :: inet:ip_address() | string()).
%% 定义listen_on() 可能类型是inet:port_number(),或者是ip和port的元组
-type(listen_on() :: inet:port_number() | {host(), inet:port_number()}).
%% 导出接口
-export_type([proto/0, transport/0, udp_transport/0, sock/0, sock_fun/0, mfargs/0, option/0, listen_on/0]).

%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------

%% @doc Start esockd application.
-spec(start() -> ok).
start() ->
    {ok, _} = application:ensure_all_started(esockd), ok.

%% @doc Open a TCP or SSL listener
%% Proto: 该参数是一个TCP服务的名称,
%% listen_on(): 是一个类型定义: -type(listen_on() :: inet:port_number() | {host(), inet:port_number()}). 服务的port或者服务器的ip和port
%% [option()]:这是一个数组参数,option()是一个类型定义:是关于TCP服务器的一些配置
%% mfargs类型定义:-type(mfargs() :: atom() | {atom(), atom()} | {module(), atom(), [term()]}).
%% 函数返回值:{ok,pid()} 或者{error,term()}
-spec(open(atom(), listen_on(), [option()], mfargs()) -> {ok, pid()} | {error, term()}).
%% 判断Proto是不是原子,Port是不是一个整数
open(Proto, Port, Opts, MFA) when is_atom(Proto), is_integer(Port) ->
%%    调用 esockd_sup 模块的start_listener 方法,比如:
%%    Opts = [{acceptors, 1}, {max_connections, 1024}, {tcp_options, [binary, {reuseaddr, true}]}].
%%    MFA = {echo_server, start_link, []},
%%    esockd:open(echo, 5000, Options, MFArgs).
	  esockd_sup:start_listener(Proto, Port, Opts, MFA);

%% 该方法跟上面的方法唯一区别就是第一个方法传递一个端口port就可以,该方法传递了一个元组{Host,Port}
open(Proto, {Host, Port}, Opts, MFA) when is_atom(Proto), is_integer(Port) ->
    %% 判断host和port,返回ip和port
    {IPAddr, _Port} = fixaddr({Host, Port}),
%%    通过ip获取和tcp配置的参数
    case proplists:get_value(ip, tcp_options(Opts)) of
%%        如果没有定义,返回ok
        undefined -> ok;
%%        如果有定义,返回ok
        IPAddr    -> ok;
%%        如果没有,就返回匹配错误
        Other     -> error({badmatch, Other})
    end,
%%    调用esockd_sup模块start_listener方法
	esockd_sup:start_listener(Proto, {IPAddr, Port}, Opts, MFA).

tcp_options(Opts) ->
    proplists:get_value(tcp_options, Opts, []).

%% 打开udp服务
%% Proto:属性名 Port:端口 Opts:配置 MFA:模块,方法,参数元组

open_udp(Proto, Port, Opts, MFA) ->
    esockd_sup:start_child(udp_child_spec(Proto, Port, Opts, MFA)).

udp_child_spec(Proto, Port, Opts, MFA) ->
    esockd_sup:udp_child_spec(Proto, fixaddr(Port), udp_options(Opts), MFA).

%% udp option参数获取
udp_options(Opts) ->
    proplists:get_value(udp_options, Opts, []).

%% 开启udp tls服务
open_dtls(Proto, ListenOn, Opts, MFA) ->
    esockd_sup:start_child(dtls_child_spec(Proto, ListenOn, Opts, MFA)).
%% udp tsl 子进程规范
dtls_child_spec(Proto, ListenOn, Opts, MFA) ->
    esockd_sup:dtls_child_spec(Proto, fixaddr(ListenOn), Opts, MFA).

%% @doc Child spec for a listener
-spec(child_spec(atom(), listen_on(), [option()], mfargs()) -> supervisor:child_spec()).
child_spec(Proto, ListenOn, Opts, MFA) when is_atom(Proto) ->
    esockd_sup:child_spec(Proto, fixaddr(ListenOn), Opts, MFA).

%%关闭监听
-spec(close({atom(), listen_on()}) -> ok | {error, term()}).
close({Proto, ListenOn}) when is_atom(Proto) ->
    close(Proto, ListenOn).

-spec(close(atom(), listen_on()) -> ok | {error, term()}).
close(Proto, ListenOn) when is_atom(Proto) ->
	esockd_sup:stop_listener(Proto, fixaddr(ListenOn)).

%% @doc Reopen the listener 从新打开监听
-spec(reopen({atom(), listen_on()}) -> {ok, pid()} | {error, term()}).
reopen({Proto, ListenOn}) when is_atom(Proto) ->
    reopen(Proto, ListenOn).

-spec(reopen(atom(), listen_on()) -> {ok, pid()} | {error, term()}).
reopen(Proto, ListenOn) when is_atom(Proto) ->
    esockd_sup:restart_listener(Proto, fixaddr(ListenOn)).

%% @doc Get listeners.
-spec(listeners() -> [{{atom(), listen_on()}, pid()}]).
listeners() -> esockd_sup:listeners().

%% @doc Get one listener.
-spec(listener({atom(), listen_on()}) -> pid() | undefined).
listener({Proto, ListenOn}) when is_atom(Proto) ->
    esockd_sup:listener({Proto, fixaddr(ListenOn)}).

%% @doc Get stats
-spec(get_stats({atom(), listen_on()}) -> [{atom(), non_neg_integer()}]).
get_stats({Proto, ListenOn}) when is_atom(Proto) ->
    esockd_server:get_stats({Proto, fixaddr(ListenOn)}).

%% @doc Get options
-spec(get_options({atom(), listen_on()}) -> undefined | pos_integer()).
get_options({Proto, ListenOn}) when is_atom(Proto) ->
    with_listener({Proto, ListenOn}, fun get_options/1);
get_options(LSup) when is_pid(LSup) ->
    esockd_listener:options(esockd_listener_sup:listener(LSup)).

%% 获取socket接收者数量
-spec(get_acceptors({atom(), listen_on()}) -> undefined | pos_integer()).
get_acceptors({Proto, ListenOn}) ->
    with_listener({Proto, ListenOn}, fun get_acceptors/1);
get_acceptors(LSup) when is_pid(LSup) ->
    AcceptorSup = esockd_listener_sup:acceptor_sup(LSup),
    esockd_acceptor_sup:count_acceptors(AcceptorSup).

%% 得到最大的连接数
-spec(get_max_connections({atom(), listen_on()} | pid()) -> undefined | pos_integer()).
get_max_connections({Proto, ListenOn}) when is_atom(Proto) ->
    with_listener({Proto, ListenOn}, fun get_max_connections/1);
get_max_connections(LSup) when is_pid(LSup) ->
    ConnSup = esockd_listener_sup:connection_sup(LSup),
    esockd_connection_sup:get_max_connections(ConnSup).

%% 调用接口设置应用最大的连接数
-spec(set_max_connections({atom(), listen_on()} | pid(), pos_integer()) -> undefined | pos_integer()).
set_max_connections({Proto, ListenOn}, MaxConns) when is_atom(Proto) ->
    with_listener({Proto, ListenOn}, fun set_max_connections/2, [MaxConns]);
set_max_connections(LSup, MaxConns) when is_pid(LSup) ->
    ConnSup = esockd_listener_sup:connection_sup(LSup),
    esockd_connection_sup:set_max_connections(ConnSup, MaxConns).

%% 获取当前系统的连接数
-spec(get_current_connections({atom(), listen_on()}) -> undefined | non_neg_integer()).
get_current_connections({Proto, ListenOn}) when is_atom(Proto) ->
    with_listener({Proto, ListenOn}, fun get_current_connections/1);
get_current_connections(LSup) when is_pid(LSup) ->
    ConnSup = esockd_listener_sup:connection_sup(LSup),
    esockd_connection_sup:count_connections(ConnSup).

%% @doc Get shutdown count
-spec(get_shutdown_count({atom(), listen_on()}) -> undefined | pos_integer()).
get_shutdown_count({Proto, ListenOn}) when is_atom(Proto) ->
    with_listener({Proto, ListenOn}, fun get_shutdown_count/1);
get_shutdown_count(LSup) when is_pid(LSup) ->
    ConnSup = esockd_listener_sup:connection_sup(LSup),
    esockd_connection_sup:get_shutdown_count(ConnSup).

%% @doc Get access rules
-spec(get_access_rules({atom(), listen_on()}) -> [esockd_access:rule()] | undefined).
get_access_rules({Proto, ListenOn}) when is_atom(Proto) ->
    with_listener({Proto, ListenOn}, fun get_access_rules/1);
get_access_rules(LSup) when is_pid(LSup) ->
    ConnSup = esockd_listener_sup:connection_sup(LSup),
    esockd_connection_sup:access_rules(ConnSup).

%% @doc Allow access address
-spec(allow({atom(), listen_on()}, all | esockd_cidr:cidr_string()) -> ok | {error, term()}).
allow({Proto, ListenOn}, CIDR) when is_atom(Proto) ->
    LSup = listener({Proto, ListenOn}),
    ConnSup = esockd_listener_sup:connection_sup(LSup),
    esockd_connection_sup:allow(ConnSup, CIDR).

%% @doc Deny access address
-spec(deny({atom(), listen_on()}, all | esockd_cidr:cidr_string()) -> ok | {error, term()}).
deny({Proto, ListenOn}, CIDR) when is_atom(Proto) ->
    LSup = listener({Proto, ListenOn}),
    ConnSup = esockd_listener_sup:connection_sup(LSup),
    esockd_connection_sup:deny(ConnSup, CIDR).

%% @doc Parse option.
parse_opt(Options) ->
    parse_opt(Options, []).
parse_opt([], Acc) ->
    lists:reverse(Acc);
parse_opt([{acceptors, I}|Opts], Acc) when is_integer(I) ->
    parse_opt(Opts, [{acceptors, I}|Acc]);
parse_opt([{max_connections, I}|Opts], Acc) when is_integer(I) ->
    parse_opt(Opts, [{max_connections, I}|Acc]);
parse_opt([{max_conn_rate, Limit}|Opts], Acc) when Limit > 0 ->
    parse_opt(Opts, [{max_conn_rate, {Limit, 1}}|Acc]);
parse_opt([{max_conn_rate, {Limit, Period}}|Opts], Acc) when Limit > 0, Period >0 ->
    parse_opt(Opts, [{max_conn_rate, {Limit, Period}}|Acc]);
parse_opt([{access_rules, Rules}|Opts], Acc) ->
    parse_opt(Opts, [{access_rules, Rules}|Acc]);
parse_opt([{shutdown, I}|Opts], Acc) when I == brutal_kill; I == infinity; is_integer(I) ->
    parse_opt(Opts, [{shutdown, I}|Acc]);
parse_opt([tune_buffer|Opts], Acc) ->
    parse_opt(Opts, [{tune_buffer, true}|Acc]);
parse_opt([{tune_buffer, I}|Opts], Acc) when is_boolean(I) ->
    parse_opt(Opts, [{tune_buffer, I}|Acc]);
parse_opt([proxy_protocol|Opts], Acc) ->
    parse_opt(Opts, [{proxy_protocol, true}|Acc]);
parse_opt([{proxy_protocol, I}|Opts], Acc) when is_boolean(I) ->
    parse_opt(Opts, [{proxy_protocol, I}|Acc]);
parse_opt([{proxy_protocol_timeout, Timeout}|Opts], Acc) when is_integer(Timeout) ->
    parse_opt(Opts, [{proxy_protocol_timeout, Timeout}|Acc]);
parse_opt([{ssl_options, L}|Opts], Acc) when is_list(L) ->
    parse_opt(Opts, [{ssl_options, L}|Acc]);
parse_opt([{tcp_options, L}|Opts], Acc) when is_list(L) ->
    parse_opt(Opts, [{tcp_options, L}|Acc]);
parse_opt([{udp_options, L}|Opts], Acc) when is_list(L) ->
    parse_opt(Opts, [{udp_options, L}|Acc]);
parse_opt([{dtls_options, L}|Opts], Acc) when is_list(L) ->
    parse_opt(Opts, [{dtls_options, L}|Acc]);
parse_opt([_|Opts], Acc) ->
    parse_opt(Opts, Acc).

%% @doc System 'ulimit -n'
-spec(ulimit() -> pos_integer()).
ulimit() ->
    proplists:get_value(max_fds, erlang:system_info(check_io)).

with_listener({Proto, ListenOn}, Fun) ->
    with_listener({Proto, ListenOn}, Fun, []).

with_listener({Proto, ListenOn}, Fun, Args) ->
    LSup = listener({Proto, ListenOn}),
    with_listener(LSup, Fun, Args);
with_listener(undefined, _Fun, _Args) ->
    undefined;
with_listener(LSup, Fun, Args) when is_pid(LSup) ->
    erlang:apply(Fun, [LSup | Args]).

-spec(to_string(listen_on()) -> string()).
%% 端口port integer 转 string
to_string(Port) when is_integer(Port) ->
    integer_to_list(Port);
%% 端口port和 ip地址合成一个ip:port的string
to_string({Addr, Port}) ->
    {IPAddr, Port} = fixaddr({Addr, Port}),
    inet:ntoa(IPAddr) ++ ":" ++ integer_to_list(Port).

%% 检查Port 是不是整数
fixaddr(Port) when is_integer(Port) ->
    Port;
%% 检查Addr是不是一个list,Port是不是整数
fixaddr({Addr, Port}) when is_list(Addr), is_integer(Port) ->
%%    解析地址,然后返回一个ip
    {ok, IPAddr} = inet:parse_address(Addr),
%%    返回元组{ip,port}
    {IPAddr, Port};
fixaddr({Addr, Port}) when is_tuple(Addr), is_integer(Port) ->
    case esockd_cidr:is_ipv6(Addr) or esockd_cidr:is_ipv4(Addr) of
        true  -> {Addr, Port};
        false -> error(invalid_ipaddr)
    end.

当在shell里执行ok=esockd:start().的时候就会调用该模块的方法:

%% @doc Start esockd application.
-spec(start() -> ok).
start() ->
    {ok, _} = application:ensure_all_started(esockd), ok.

然后接着执行esockd_app.erl 模块里方法:

start(_StartType, _StartArgs) ->
    io:format(" esockd_app start ~n"),
    esockd_sup:start_link().

下一篇介绍 esockd_sup模块,该模块作为应用的root 监听者。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值