-module(emqx_tcp_app).
-behaviour(application).
%% 插件协议
-emqx_plugin(protocol).
%% 导出启动和停止方法
-export([start/2, stop/1]).
%% 导出socket监听和停止方法
-export([start_listener/1,start_listener/3,stop_listener/1,stop_listener/3]).
%% 引入mqtt头文件
-include("emqx_mqtt.hrl").
%% 定义mqtt报文的头部信息
-record(mqtt_packet_header, {type = 0, dup = false, qos = 0, retain = false}).
%% 连接报文定义
-record(mqtt_packet_connect,
{
proto_name = <<"MQTT">>,
proto_ver = 4,is_bridge = false,
clean_start = true,will_flag = false,
will_qos = 0,will_retain = false,
keepalive = 0,
properties = #{},
clientid = <<>>,
will_props = #{},
will_topic = undefined,
will_payload = undefined,
username = undefined,
password = undefined}).
%% 连接回复报文
-record(mqtt_packet_connack,{ack_flags, reason_code, properties = #{}}).
%% 发布消息
-record(mqtt_packet_publish,{topic_name, packet_id, properties = #{}}).
%% 发布回复报文
-record(mqtt_packet_puback,{packet_id, reason_code, properties = #{}}).
%% 订阅报文
-record(mqtt_packet_subscribe,{packet_id, properties = #{}, topic_filters})
## 订阅回复报文
-record(mqtt_packet_suback,{packet_id, properties = #{}, reason_codes}).
%% 取消订阅报文
-record(mqtt_packet_unsubscribe,{packet_id, properties = #{}, topic_filters})
%% 取消订阅回复报文
-record(mqtt_packet_unsuback,{packet_id, properties = #{}, reason_codes}).
%% 断开连接报文
-record(mqtt_packet_disconnect,{reason_code, properties = #{}}).
%% 授权报文
-record(mqtt_packet_auth,{reason_code, properties = #{}}).
%% mqtt 数据包定义{头部报文,命令字,消息质量,消息负载}
-record(mqtt_packet,
{header :: #mqtt_packet_header{},
variable ::
#mqtt_packet_connect{} |
#mqtt_packet_connack{} |
#mqtt_packet_publish{} |
#mqtt_packet_puback{} |
#mqtt_packet_subscribe{} |
#mqtt_packet_suback{} |
#mqtt_packet_unsubscribe{} |
#mqtt_packet_unsuback{} |
#mqtt_packet_disconnect{} |
#mqtt_packet_auth{} |
pos_integer() |
undefined,
payload :: binary() | undefined}).
%% mqtt 消息定义
-record(mqtt_msg,{
qos = 0, %% 消息质量
retain = false,%% 保留最新消息
dup = false, %%
packet_id, %% 消息id
topic, %% 消息主题
props, %%
payload %% 消息负载
}).
%% 引入模块的头文件
-include("emqx_tcp.hrl").
%% 定义监听类型{协议,简单方法,监听配置}
-type listener() :: {esockd:proto(), esockd:listen_on(),[esockd:option()]}.
%% 启动application模块
start(_Type, _Args) ->
start_listener(),
emqx_tcp_sup:start_link().
%% 停止
stop(_State) -> stop_listener().
%% 循环启动多个监听
start_listener() ->
lists:foreach(fun start_listener/1, listeners_confs()).
%% 循环停止多个监听
stop_listener() ->
lists:foreach(fun stop_listener/1, listeners_confs()).
%% 定义启动监听方法,返回进程id或者错误原子信息
-spec start_listener(listener()) -> {ok, pid()} |{error, term()}.
start_listener({Proto, ListenOn, Options}) ->
%% 调用启动方法,反悔ok或者error
case start_listener(Proto, ListenOn, Options) of
{ok, _} ->
io:format("Start emqx-tcp:~s listener on[Proto, format(ListenOn)]);
{error, Reason} ->
io:format(standard_error,
"Failed to start emqx-tcp:~s listener "
"on ~s - ~p~n!",
[Proto, format(ListenOn), Reason])
end.
%% 启动监听
-spec start_listener(esockd:proto(), esockd:listen_on(),
[esockd:option()]) -> {ok, pid()} | {error, term()}.
start_listener(tcp, ListenOn, Options) ->
start_tcp_listener('emqx-tcp:tcp', ListenOn, Options);
start_listener(ssl, ListenOn, Options) ->
start_tcp_listener('emqx-tcp:ssl', ListenOn, Options).
start_tcp_listener(Name, ListenOn, Options) ->
SockOpts = esockd:parse_opt(Options),
esockd:open(Name,ListenOn,merge_default(SockOpts),
{emqx_tcp_connection,start_link,[Options -- SockOpts]}).
%% 合并配置项
merge_default(Options) ->
case lists:keytake(tcp_options, 1, Options) of
{value, {tcp_options, TcpOpts}, Options1} ->
[{tcp_options,
emqx_misc:merge_opts([binary,{packet, raw},{reuseaddr, true}{backlog, 512},{nodelay, true}],TcpOpts)}| Options1];
false ->
[{tcp_options,{packet, raw},{reuseaddr, true},{backlog, 512},
{nodelay, true}]}| Options]
end.
%% 监听配置
listeners_confs() ->
[{Proto, ListenOn, wrap_proto_options(Options)} || {Proto, ListenOn, Options} <- env(listeners, [])].
wrap_proto_options(Opts) ->
ProtoOpts = [{K, env(K)} || K <- protokeys(), env(K) =/= undefined],
ProtoOpts ++ Opts.
protokeys() ->
[idle_timeout,
up_topic, %% 上行主题
dn_topic, %% 下行主题
max_packet_size,%% 数据包大小
enable_stats,%% 是否开启统计
force_gc_policy,%% 虚拟机垃圾回收策略
force_shutdown_policy].%% 强制杀死策略
%% 停止监听
-spec stop_listener(listener()) -> ok | {error, term()}.
stop_listener({Proto, ListenOn, Opts}) ->
case stop_listener(Proto, ListenOn, Opts) of
ok ->
io:format("Stop emqx-tcp:~s listener on ~s successfully.~n",
[Proto, format(ListenOn)]);
{error, Reason} ->
io:format(standard_error,
"Failed to stop emqx-tcp:~s listener "
"on ~s - ~p~n.",
[Proto, format(ListenOn), Reason])
end.
%% 停止监听
-spec stop_listener(esockd:proto(), esockd:listen_on(),
[esockd:option()]) -> ok | {error, term()}.
stop_listener(tcp, ListenOn, _Opts) ->
esockd:close('emqx-tcp:tcp', ListenOn);
stop_listener(Proto, ListenOn, _Opts)
when Proto == ssl; Proto == tls ->
esockd:close('emqx-tcp:ssl', ListenOn).
%% 端口
format(Port) when is_integer(Port) ->
io_lib:format("0.0.0.0:~w", [Port]);
%% ip地址和端口
format({Addr, Port}) when is_list(Addr) ->
io_lib:format("~s:~w", [Addr, Port]);
format({Addr, Port}) when is_tuple(Addr) ->
io_lib:format("~s:~w", [inet:ntoa(Addr), Port]).
env(Key) -> env(Key, undefined).
env(Key, Default) ->
application:get_env(emqx_tcp, Key, Default).
上述代码是对emqx 私有tcp协议 建立tcp连接,主要是基于esockd这个socket连接池来构建 里面牵涉到几个文件,下面介绍emqx_tcp.
源代码地址:https://github.com/tlchun/emqx-tcp.git