distributed session
This commit is contained in:
parent
fcc0bb98e2
commit
c85617c080
|
@ -1,24 +1,24 @@
|
||||||
%%------------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
%%% Copyright (c) 2012-2015 eMQTT.IO, All Rights Reserved.
|
||||||
%%
|
%%%
|
||||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
%% of this software and associated documentation files (the "Software"), to deal
|
%%% of this software and associated documentation files (the "Software"), to deal
|
||||||
%% in the Software without restriction, including without limitation the rights
|
%%% in the Software without restriction, including without limitation the rights
|
||||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
%% copies of the Software, and to permit persons to whom the Software is
|
%%% copies of the Software, and to permit persons to whom the Software is
|
||||||
%% furnished to do so, subject to the following conditions:
|
%%% furnished to do so, subject to the following conditions:
|
||||||
%%
|
%%%
|
||||||
%% The above copyright notice and this permission notice shall be included in all
|
%%% The above copyright notice and this permission notice shall be included in all
|
||||||
%% copies or substantial portions of the Software.
|
%%% copies or substantial portions of the Software.
|
||||||
%%
|
%%%
|
||||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
%% SOFTWARE.
|
%%% SOFTWARE.
|
||||||
%%------------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
%%% @doc
|
%%% @doc
|
||||||
%%% MQTT Broker Header.
|
%%% MQTT Broker Header.
|
||||||
%%%
|
%%%
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-record(mqtt_queue, {
|
-record(mqtt_queue, {
|
||||||
name :: binary(),
|
name :: binary(),
|
||||||
subpid :: pid(),
|
qpid :: pid(),
|
||||||
qos = 0 :: 0 | 1 | 2
|
qos = 0 :: 0 | 1 | 2
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
@ -83,12 +83,15 @@
|
||||||
%% MQTT Client
|
%% MQTT Client
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-record(mqtt_client, {
|
-record(mqtt_client, {
|
||||||
client_id :: binary() | undefined,
|
client_id :: binary() | undefined,
|
||||||
username :: binary() | undefined,
|
client_pid :: pid(),
|
||||||
ipaddress :: inet:ip_address(),
|
username :: binary() | undefined,
|
||||||
clean_sess :: boolean(),
|
peername :: {inet:ip_address(), integer()},
|
||||||
client_pid :: pid(),
|
clean_sess :: boolean(),
|
||||||
proto_ver :: 3 | 4
|
proto_ver :: 3 | 4,
|
||||||
|
keepalive = 0,
|
||||||
|
will_topic :: undefined | binary(),
|
||||||
|
connected_at :: erlang:timestamp()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type mqtt_client() :: #mqtt_client{}.
|
-type mqtt_client() :: #mqtt_client{}.
|
||||||
|
@ -98,8 +101,9 @@
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-record(mqtt_session, {
|
-record(mqtt_session, {
|
||||||
client_id,
|
client_id,
|
||||||
session_pid,
|
sess_pid,
|
||||||
subscriptions = []
|
persistent,
|
||||||
|
on_node
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type mqtt_session() :: #mqtt_session{}.
|
-type mqtt_session() :: #mqtt_session{}.
|
||||||
|
@ -111,8 +115,8 @@
|
||||||
-type mqtt_pktid() :: 1..16#ffff | undefined.
|
-type mqtt_pktid() :: 1..16#ffff | undefined.
|
||||||
|
|
||||||
-record(mqtt_message, {
|
-record(mqtt_message, {
|
||||||
msgid :: mqtt_msgid(), %% Unique Message ID
|
msgid :: mqtt_msgid(), %% Global unique message ID
|
||||||
pktid :: 1..16#ffff, %% PacketId
|
pktid :: mqtt_pktid(), %% PacketId
|
||||||
topic :: binary(), %% Topic that the message is published to
|
topic :: binary(), %% Topic that the message is published to
|
||||||
from :: binary() | atom(), %% ClientId of publisher
|
from :: binary() | atom(), %% ClientId of publisher
|
||||||
qos = 0 :: 0 | 1 | 2, %% Message QoS
|
qos = 0 :: 0 | 1 | 2, %% Message QoS
|
||||||
|
|
|
@ -230,4 +230,3 @@ is_running(Node) ->
|
||||||
Pid when is_pid(Pid) -> true
|
Pid when is_pid(Pid) -> true
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -102,8 +102,8 @@ check_acl(#mqtt_client{client_id = ClientId}, PubSub, Topic, []) ->
|
||||||
allow;
|
allow;
|
||||||
check_acl(Client, PubSub, Topic, [{M, State}|AclMods]) ->
|
check_acl(Client, PubSub, Topic, [{M, State}|AclMods]) ->
|
||||||
case M:check_acl({Client, PubSub, Topic}, State) of
|
case M:check_acl({Client, PubSub, Topic}, State) of
|
||||||
allow -> allow;
|
allow -> allow;
|
||||||
deny -> deny;
|
deny -> deny;
|
||||||
ignore -> check_acl(Client, PubSub, Topic, AclMods)
|
ignore -> check_acl(Client, PubSub, Topic, AclMods)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -114,9 +114,9 @@ match_who(#mqtt_client{client_id = ClientId}, {client, ClientId}) ->
|
||||||
true;
|
true;
|
||||||
match_who(#mqtt_client{username = Username}, {user, Username}) ->
|
match_who(#mqtt_client{username = Username}, {user, Username}) ->
|
||||||
true;
|
true;
|
||||||
match_who(#mqtt_client{ipaddress = undefined}, {ipaddr, _Tup}) ->
|
match_who(#mqtt_client{peername = undefined}, {ipaddr, _Tup}) ->
|
||||||
false;
|
false;
|
||||||
match_who(#mqtt_client{ipaddress = IP}, {ipaddr, {_CDIR, Start, End}}) ->
|
match_who(#mqtt_client{peername = {IP, _}}, {ipaddr, {_CDIR, Start, End}}) ->
|
||||||
I = esockd_access:atoi(IP),
|
I = esockd_access:atoi(IP),
|
||||||
I >= Start andalso I =< End;
|
I >= Start andalso I =< End;
|
||||||
match_who(_Client, _Who) ->
|
match_who(_Client, _Who) ->
|
||||||
|
|
|
@ -70,6 +70,7 @@ print_vsn() ->
|
||||||
start_servers(Sup) ->
|
start_servers(Sup) ->
|
||||||
Servers = [{"emqttd trace", emqttd_trace},
|
Servers = [{"emqttd trace", emqttd_trace},
|
||||||
{"emqttd pooler", {supervisor, emqttd_pooler_sup}},
|
{"emqttd pooler", {supervisor, emqttd_pooler_sup}},
|
||||||
|
{"emqttd client manager", {supervisor, emqttd_cm_sup}},
|
||||||
{"emqttd session manager", {supervisor, emqttd_sm_sup}},
|
{"emqttd session manager", {supervisor, emqttd_sm_sup}},
|
||||||
{"emqttd session supervisor", {supervisor, emqttd_session_sup}},
|
{"emqttd session supervisor", {supervisor, emqttd_session_sup}},
|
||||||
{"emqttd pubsub", {supervisor, emqttd_pubsub_sup}},
|
{"emqttd pubsub", {supervisor, emqttd_pubsub_sup}},
|
||||||
|
|
|
@ -101,9 +101,9 @@ init(Opts) ->
|
||||||
|
|
||||||
check(#mqtt_client{client_id = undefined}, _Password, []) ->
|
check(#mqtt_client{client_id = undefined}, _Password, []) ->
|
||||||
{error, "ClientId undefined"};
|
{error, "ClientId undefined"};
|
||||||
check(#mqtt_client{client_id = ClientId, ipaddress = IpAddress}, _Password, []) ->
|
check(#mqtt_client{client_id = ClientId, peername = {IpAddress, _}}, _Password, []) ->
|
||||||
check_clientid_only(ClientId, IpAddress);
|
check_clientid_only(ClientId, IpAddress);
|
||||||
check(#mqtt_client{client_id = ClientId, ipaddress = IpAddress}, _Password, [{password, no}|_]) ->
|
check(#mqtt_client{client_id = ClientId, peername = {IpAddress, _}}, _Password, [{password, no}|_]) ->
|
||||||
check_clientid_only(ClientId, IpAddress);
|
check_clientid_only(ClientId, IpAddress);
|
||||||
check(_Client, undefined, [{password, yes}|_]) ->
|
check(_Client, undefined, [{password, yes}|_]) ->
|
||||||
{error, "Password undefined"};
|
{error, "Password undefined"};
|
||||||
|
|
|
@ -46,7 +46,8 @@
|
||||||
-record(state, {node, subtopic,
|
-record(state, {node, subtopic,
|
||||||
qos,
|
qos,
|
||||||
topic_suffix = <<>>,
|
topic_suffix = <<>>,
|
||||||
topic_prefix = <<>>,
|
topic_prefix = <<>>,
|
||||||
|
mqueue = emqttd_mqueue:mqueue(),
|
||||||
max_queue_len = 0,
|
max_queue_len = 0,
|
||||||
ping_down_interval = ?PING_DOWN_INTERVAL,
|
ping_down_interval = ?PING_DOWN_INTERVAL,
|
||||||
status = up}).
|
status = up}).
|
||||||
|
@ -81,8 +82,11 @@ init([Node, SubTopic, Options]) ->
|
||||||
true ->
|
true ->
|
||||||
true = erlang:monitor_node(Node, true),
|
true = erlang:monitor_node(Node, true),
|
||||||
State = parse_opts(Options, #state{node = Node, subtopic = SubTopic}),
|
State = parse_opts(Options, #state{node = Node, subtopic = SubTopic}),
|
||||||
|
MQueue = emqttd_mqueue:new(qname(Node, SubTopic),
|
||||||
|
[{max_len, State#state.max_queue_len}],
|
||||||
|
emqttd_alarm:alarm_fun()),
|
||||||
emqttd_pubsub:subscribe({SubTopic, State#state.qos}),
|
emqttd_pubsub:subscribe({SubTopic, State#state.qos}),
|
||||||
{ok, State};
|
{ok, State#state{mqueue = MQueue}};
|
||||||
false ->
|
false ->
|
||||||
{stop, {cannot_connect, Node}}
|
{stop, {cannot_connect, Node}}
|
||||||
end.
|
end.
|
||||||
|
@ -100,15 +104,19 @@ parse_opts([{max_queue_len, Len} | Opts], State) ->
|
||||||
parse_opts([{ping_down_interval, Interval} | Opts], State) ->
|
parse_opts([{ping_down_interval, Interval} | Opts], State) ->
|
||||||
parse_opts(Opts, State#state{ping_down_interval = Interval*1000}).
|
parse_opts(Opts, State#state{ping_down_interval = Interval*1000}).
|
||||||
|
|
||||||
|
qname(Node, SubTopic) when is_atom(Node) ->
|
||||||
|
qname(atom_to_list(Node), SubTopic);
|
||||||
|
qname(Node, SubTopic) ->
|
||||||
|
list_to_binary(["Bridge:", Node, ":", SubTopic]).
|
||||||
|
|
||||||
handle_call(_Request, _From, State) ->
|
handle_call(_Request, _From, State) ->
|
||||||
{reply, error, State}.
|
{reply, error, State}.
|
||||||
|
|
||||||
handle_cast(_Msg, State) ->
|
handle_cast(_Msg, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info({dispatch, Msg}, State = #state{node = Node, status = down}) ->
|
handle_info({dispatch, Msg}, State = #state{mqueue = MQ, status = down}) ->
|
||||||
lager:error("Bridge Dropped Msg for ~p Down: ~s", [Node, emqttd_message:format(Msg)]),
|
{noreply, State#state{mqueue = emqttd_mqueue:in(Msg, MQ)}};
|
||||||
{noreply, State};
|
|
||||||
|
|
||||||
handle_info({dispatch, Msg}, State = #state{node = Node, status = up}) ->
|
handle_info({dispatch, Msg}, State = #state{node = Node, status = up}) ->
|
||||||
rpc:cast(Node, emqttd_pubsub, publish, [transform(Msg, State)]),
|
rpc:cast(Node, emqttd_pubsub, publish, [transform(Msg, State)]),
|
||||||
|
@ -124,7 +132,7 @@ handle_info({nodeup, Node}, State = #state{node = Node}) ->
|
||||||
case emqttd:is_running(Node) of
|
case emqttd:is_running(Node) of
|
||||||
true ->
|
true ->
|
||||||
lager:warning("Bridge Node Up: ~p", [Node]),
|
lager:warning("Bridge Node Up: ~p", [Node]),
|
||||||
{noreply, State#state{status = up}};
|
{noreply, dequeue(State#state{status = up})};
|
||||||
false ->
|
false ->
|
||||||
self() ! {nodedown, Node},
|
self() ! {nodedown, Node},
|
||||||
{noreply, State#state{status = down}}
|
{noreply, State#state{status = down}}
|
||||||
|
@ -159,6 +167,15 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%%% Internal functions
|
%%% Internal functions
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
|
|
||||||
|
dequeue(State = #state{mqueue = MQ}) ->
|
||||||
|
case emqttd_mqueue:out(MQ) of
|
||||||
|
{empty, MQ1} ->
|
||||||
|
State#state{mqueue = MQ1};
|
||||||
|
{{value, Msg}, MQ1} ->
|
||||||
|
handle_info({dispatch, Msg}, State),
|
||||||
|
dequeue(State#state{mqueue = MQ1})
|
||||||
|
end.
|
||||||
|
|
||||||
transform(Msg = #mqtt_message{topic = Topic}, #state{topic_prefix = Prefix,
|
transform(Msg = #mqtt_message{topic = Topic}, #state{topic_prefix = Prefix,
|
||||||
topic_suffix = Suffix}) ->
|
topic_suffix = Suffix}) ->
|
||||||
Msg#mqtt_message{topic = <<Prefix/binary, Topic/binary, Suffix/binary>>}.
|
Msg#mqtt_message{topic = <<Prefix/binary, Topic/binary, Suffix/binary>>}.
|
||||||
|
|
|
@ -48,7 +48,6 @@
|
||||||
start_link() ->
|
start_link() ->
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||||
|
|
||||||
%%TODO: bridges...
|
|
||||||
-spec bridges() -> [{tuple(), pid()}].
|
-spec bridges() -> [{tuple(), pid()}].
|
||||||
bridges() ->
|
bridges() ->
|
||||||
[{{Node, SubTopic}, Pid} || {{bridge, Node, SubTopic}, Pid, worker, _}
|
[{{Node, SubTopic}, Pid} || {{bridge, Node, SubTopic}, Pid, worker, _}
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
|
|
||||||
-define(BROKER_TAB, mqtt_broker).
|
-define(BROKER_TAB, mqtt_broker).
|
||||||
|
|
||||||
-record(state, {started_at, sys_interval, tick_tref}).
|
-record(state, {started_at, sys_interval, heartbeat, tick_tref}).
|
||||||
|
|
||||||
%% $SYS Topics of Broker
|
%% $SYS Topics of Broker
|
||||||
-define(SYSTOP_BROKERS, [
|
-define(SYSTOP_BROKERS, [
|
||||||
|
@ -224,7 +224,9 @@ init([]) ->
|
||||||
emqttd_pubsub:create(<<"$SYS/brokers">>),
|
emqttd_pubsub:create(<<"$SYS/brokers">>),
|
||||||
[ok = create_topic(Topic) || Topic <- ?SYSTOP_BROKERS],
|
[ok = create_topic(Topic) || Topic <- ?SYSTOP_BROKERS],
|
||||||
% Tick
|
% Tick
|
||||||
{ok, #state{started_at = os:timestamp(), tick_tref = start_tick(tick)}, hibernate}.
|
{ok, #state{started_at = os:timestamp(),
|
||||||
|
heartbeat = start_tick(1000, heartbeat),
|
||||||
|
tick_tref = start_tick(tick)}, hibernate}.
|
||||||
|
|
||||||
handle_call(uptime, _From, State) ->
|
handle_call(uptime, _From, State) ->
|
||||||
{reply, uptime(State), State};
|
{reply, uptime(State), State};
|
||||||
|
@ -260,18 +262,22 @@ handle_call(_Request, _From, State) ->
|
||||||
handle_cast(_Msg, State) ->
|
handle_cast(_Msg, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info(tick, State) ->
|
handle_info(heartbeat, State) ->
|
||||||
retain(brokers),
|
|
||||||
retain(version, list_to_binary(version())),
|
|
||||||
retain(sysdescr, list_to_binary(sysdescr())),
|
|
||||||
publish(uptime, list_to_binary(uptime(State))),
|
publish(uptime, list_to_binary(uptime(State))),
|
||||||
publish(datetime, list_to_binary(datetime())),
|
publish(datetime, list_to_binary(datetime())),
|
||||||
{noreply, State, hibernate};
|
{noreply, State, hibernate};
|
||||||
|
|
||||||
|
handle_info(tick, State) ->
|
||||||
|
retain(brokers),
|
||||||
|
retain(version, list_to_binary(version())),
|
||||||
|
retain(sysdescr, list_to_binary(sysdescr())),
|
||||||
|
{noreply, State, hibernate};
|
||||||
|
|
||||||
handle_info(_Info, State) ->
|
handle_info(_Info, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
terminate(_Reason, #state{tick_tref = TRef}) ->
|
terminate(_Reason, #state{heartbeat = Hb, tick_tref = TRef}) ->
|
||||||
|
stop_tick(Hb),
|
||||||
stop_tick(TRef).
|
stop_tick(TRef).
|
||||||
|
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
code_change/3, terminate/2]).
|
code_change/3, terminate/2]).
|
||||||
|
|
||||||
%%Client State...
|
%% Client State...
|
||||||
-record(state, {transport,
|
-record(state, {transport,
|
||||||
socket,
|
socket,
|
||||||
peername,
|
peername,
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
await_recv,
|
await_recv,
|
||||||
conn_state,
|
conn_state,
|
||||||
conserve,
|
conserve,
|
||||||
parse_state,
|
parser,
|
||||||
proto_state,
|
proto_state,
|
||||||
packet_opts,
|
packet_opts,
|
||||||
keepalive}).
|
keepalive}).
|
||||||
|
@ -57,18 +57,16 @@
|
||||||
start_link(SockArgs, PktOpts) ->
|
start_link(SockArgs, PktOpts) ->
|
||||||
{ok, proc_lib:spawn_link(?MODULE, init, [[SockArgs, PktOpts]])}.
|
{ok, proc_lib:spawn_link(?MODULE, init, [[SockArgs, PktOpts]])}.
|
||||||
|
|
||||||
%%TODO: rename?
|
|
||||||
info(Pid) ->
|
info(Pid) ->
|
||||||
gen_server:call(Pid, info).
|
gen_server:call(Pid, info, infinity).
|
||||||
|
|
||||||
init([SockArgs = {Transport, Sock, _SockFun}, PacketOpts]) ->
|
init([SockArgs = {Transport, Sock, _SockFun}, PacketOpts]) ->
|
||||||
%transform if ssl.
|
% Transform if ssl.
|
||||||
{ok, NewSock} = esockd_connection:accept(SockArgs),
|
{ok, NewSock} = esockd_connection:accept(SockArgs),
|
||||||
{ok, Peername} = emqttd_net:peername(Sock),
|
{ok, Peername} = emqttd_net:peername(Sock),
|
||||||
{ok, ConnStr} = emqttd_net:connection_string(Sock, inbound),
|
{ok, ConnStr} = emqttd_net:connection_string(Sock, inbound),
|
||||||
lager:info("Connect from ~s", [ConnStr]),
|
lager:info("Connect from ~s", [ConnStr]),
|
||||||
SendFun = fun(Data) -> Transport:send(NewSock, Data) end,
|
SendFun = fun(Data) -> Transport:send(NewSock, Data) end,
|
||||||
ParserState = emqttd_parser:init(PacketOpts),
|
|
||||||
ProtoState = emqttd_protocol:init(Peername, SendFun, PacketOpts),
|
ProtoState = emqttd_protocol:init(Peername, SendFun, PacketOpts),
|
||||||
State = control_throttle(#state{transport = Transport,
|
State = control_throttle(#state{transport = Transport,
|
||||||
socket = NewSock,
|
socket = NewSock,
|
||||||
|
@ -78,17 +76,16 @@ init([SockArgs = {Transport, Sock, _SockFun}, PacketOpts]) ->
|
||||||
conn_state = running,
|
conn_state = running,
|
||||||
conserve = false,
|
conserve = false,
|
||||||
packet_opts = PacketOpts,
|
packet_opts = PacketOpts,
|
||||||
parse_state = ParserState,
|
parser = emqttd_parser:new(PacketOpts),
|
||||||
proto_state = ProtoState}),
|
proto_state = ProtoState}),
|
||||||
gen_server:enter_loop(?MODULE, [], State, 10000).
|
gen_server:enter_loop(?MODULE, [], State, 10000).
|
||||||
|
|
||||||
%%TODO: Not enough...
|
handle_call(info, _From, State = #state{conn_name = ConnName,
|
||||||
handle_call(info, _From, State = #state{conn_name=ConnName,
|
|
||||||
proto_state = ProtoState}) ->
|
proto_state = ProtoState}) ->
|
||||||
{reply, [{conn_name, ConnName} | emqttd_protocol:info(ProtoState)], State};
|
{reply, [{conn_name, ConnName} | emqttd_protocol:info(ProtoState)], State};
|
||||||
|
|
||||||
handle_call(Req, _From, State = #state{peername = Peername}) ->
|
handle_call(Req, _From, State = #state{peername = Peername}) ->
|
||||||
lager:critical("Client ~s: unexpected request - ~p",[emqttd_net:format(Peername), Req]),
|
lager:critical("Client ~s: unexpected request - ~p", [emqttd_net:format(Peername), Req]),
|
||||||
{reply, {error, unsupported_request}, State}.
|
{reply, {error, unsupported_request}, State}.
|
||||||
|
|
||||||
handle_cast(Msg, State = #state{peername = Peername}) ->
|
handle_cast(Msg, State = #state{peername = Peername}) ->
|
||||||
|
@ -100,8 +97,6 @@ handle_info(timeout, State) ->
|
||||||
|
|
||||||
handle_info({stop, duplicate_id, _NewPid}, State=#state{proto_state = ProtoState,
|
handle_info({stop, duplicate_id, _NewPid}, State=#state{proto_state = ProtoState,
|
||||||
conn_name=ConnName}) ->
|
conn_name=ConnName}) ->
|
||||||
%% need transfer data???
|
|
||||||
%% emqttd_client:transfer(NewPid, Data),
|
|
||||||
lager:error("Shutdown for duplicate clientid: ~s, conn:~s",
|
lager:error("Shutdown for duplicate clientid: ~s, conn:~s",
|
||||||
[emqttd_protocol:clientid(ProtoState), ConnName]),
|
[emqttd_protocol:clientid(ProtoState), ConnName]),
|
||||||
stop({shutdown, duplicate_id}, State);
|
stop({shutdown, duplicate_id}, State);
|
||||||
|
@ -124,8 +119,7 @@ handle_info({inet_reply, _Ref, ok}, State) ->
|
||||||
handle_info({inet_async, Sock, _Ref, {ok, Data}}, State = #state{peername = Peername, socket = Sock}) ->
|
handle_info({inet_async, Sock, _Ref, {ok, Data}}, State = #state{peername = Peername, socket = Sock}) ->
|
||||||
lager:debug("RECV from ~s: ~p", [emqttd_net:format(Peername), Data]),
|
lager:debug("RECV from ~s: ~p", [emqttd_net:format(Peername), Data]),
|
||||||
emqttd_metrics:inc('bytes/received', size(Data)),
|
emqttd_metrics:inc('bytes/received', size(Data)),
|
||||||
process_received_bytes(Data,
|
received(Data, control_throttle(State #state{await_recv = false}));
|
||||||
control_throttle(State #state{await_recv = false}));
|
|
||||||
|
|
||||||
handle_info({inet_async, _Sock, _Ref, {error, Reason}}, State) ->
|
handle_info({inet_async, _Sock, _Ref, {error, Reason}}, State) ->
|
||||||
network_error(Reason, State);
|
network_error(Reason, State);
|
||||||
|
@ -170,24 +164,22 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%-------------------------------------------------------
|
%-------------------------------------------------------
|
||||||
% receive and parse tcp data
|
% receive and parse tcp data
|
||||||
%-------------------------------------------------------
|
%-------------------------------------------------------
|
||||||
process_received_bytes(<<>>, State) ->
|
received(<<>>, State) ->
|
||||||
{noreply, State, hibernate};
|
{noreply, State, hibernate};
|
||||||
|
|
||||||
process_received_bytes(Bytes, State = #state{packet_opts = PacketOpts,
|
received(Bytes, State = #state{packet_opts = PacketOpts,
|
||||||
parse_state = ParseState,
|
parser = Parser,
|
||||||
proto_state = ProtoState,
|
proto_state = ProtoState,
|
||||||
conn_name = ConnStr}) ->
|
conn_name = ConnStr}) ->
|
||||||
case emqttd_parser:parse(Bytes, ParseState) of
|
case Parser(Bytes) of
|
||||||
{more, ParseState1} ->
|
{more, NewParser} ->
|
||||||
{noreply,
|
{noreply, control_throttle(State #state{parser = NewParser}), hibernate};
|
||||||
control_throttle(State #state{parse_state = ParseState1}),
|
|
||||||
hibernate};
|
|
||||||
{ok, Packet, Rest} ->
|
{ok, Packet, Rest} ->
|
||||||
received_stats(Packet),
|
received_stats(Packet),
|
||||||
case emqttd_protocol:received(Packet, ProtoState) of
|
case emqttd_protocol:received(Packet, ProtoState) of
|
||||||
{ok, ProtoState1} ->
|
{ok, ProtoState1} ->
|
||||||
process_received_bytes(Rest, State#state{parse_state = emqttd_parser:init(PacketOpts),
|
received(Rest, State#state{parser = emqttd_parser:new(PacketOpts),
|
||||||
proto_state = ProtoState1});
|
proto_state = ProtoState1});
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
lager:error("MQTT protocol error ~p for connection ~p~n", [Error, ConnStr]),
|
lager:error("MQTT protocol error ~p for connection ~p~n", [Error, ConnStr]),
|
||||||
stop({shutdown, Error}, State);
|
stop({shutdown, Error}, State);
|
||||||
|
@ -201,7 +193,6 @@ process_received_bytes(Bytes, State = #state{packet_opts = PacketOpts,
|
||||||
stop({shutdown, Error}, State)
|
stop({shutdown, Error}, State)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%----------------------------------------------------------------------------
|
|
||||||
network_error(Reason, State = #state{peername = Peername}) ->
|
network_error(Reason, State = #state{peername = Peername}) ->
|
||||||
lager:warning("Client ~s: MQTT detected network error '~p'",
|
lager:warning("Client ~s: MQTT detected network error '~p'",
|
||||||
[emqttd_net:format(Peername), Reason]),
|
[emqttd_net:format(Peername), Reason]),
|
||||||
|
@ -244,4 +235,4 @@ inc(?DISCONNECT) ->
|
||||||
emqttd_metrics:inc('packets/disconnect');
|
emqttd_metrics:inc('packets/disconnect');
|
||||||
inc(_) ->
|
inc(_) ->
|
||||||
ignore.
|
ignore.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,151 @@
|
||||||
|
%%%-----------------------------------------------------------------------------
|
||||||
|
%%% Copyright (c) 2012-2015 eMQTT.IO, All Rights Reserved.
|
||||||
|
%%%
|
||||||
|
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
%%% of this software and associated documentation files (the "Software"), to deal
|
||||||
|
%%% in the Software without restriction, including without limitation the rights
|
||||||
|
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
%%% copies of the Software, and to permit persons to whom the Software is
|
||||||
|
%%% furnished to do so, subject to the following conditions:
|
||||||
|
%%%
|
||||||
|
%%% The above copyright notice and this permission notice shall be included in all
|
||||||
|
%%% copies or substantial portions of the Software.
|
||||||
|
%%%
|
||||||
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
%%% SOFTWARE.
|
||||||
|
%%%-----------------------------------------------------------------------------
|
||||||
|
%%% @doc
|
||||||
|
%%% MQTT Client Manager
|
||||||
|
%%%
|
||||||
|
%%% @end
|
||||||
|
%%%-----------------------------------------------------------------------------
|
||||||
|
-module(emqttd_cm).
|
||||||
|
|
||||||
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
||||||
|
-include("emqttd.hrl").
|
||||||
|
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
-define(SERVER, ?MODULE).
|
||||||
|
|
||||||
|
%% API Exports
|
||||||
|
-export([start_link/2, pool/0]).
|
||||||
|
|
||||||
|
-export([lookup/1, register/1, unregister/1]).
|
||||||
|
|
||||||
|
%% gen_server Function Exports
|
||||||
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
|
-record(state, {id, statsfun}).
|
||||||
|
|
||||||
|
-define(CM_POOL, cm_pool).
|
||||||
|
|
||||||
|
%%%=============================================================================
|
||||||
|
%%% API
|
||||||
|
%%%=============================================================================
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% @doc Start client manager
|
||||||
|
%% @end
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
-spec start_link(Id, StatsFun) -> {ok, pid()} | ignore | {error, any()} when
|
||||||
|
Id :: pos_integer(),
|
||||||
|
StatsFun :: fun().
|
||||||
|
start_link(Id, StatsFun) ->
|
||||||
|
gen_server:start_link(?MODULE, [Id, StatsFun], []).
|
||||||
|
|
||||||
|
pool() -> ?CM_POOL.
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% @doc Lookup client pid with clientId
|
||||||
|
%% @end
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
-spec lookup(ClientId :: binary()) -> mqtt_client() | undefined.
|
||||||
|
lookup(ClientId) when is_binary(ClientId) ->
|
||||||
|
case ets:lookup(mqtt_client, ClientId) of
|
||||||
|
[Client] -> Client;
|
||||||
|
[] -> undefined
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% @doc Register clientId with pid.
|
||||||
|
%% @end
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
-spec register(Client :: mqtt_client()) -> ok.
|
||||||
|
register(Client = #mqtt_client{client_id = ClientId}) ->
|
||||||
|
CmPid = gproc_pool:pick_worker(?CM_POOL, ClientId),
|
||||||
|
gen_server:cast(CmPid, {register, Client}).
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% @doc Unregister clientId with pid.
|
||||||
|
%% @end
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
-spec unregister(ClientId :: binary()) -> ok.
|
||||||
|
unregister(ClientId) when is_binary(ClientId) ->
|
||||||
|
CmPid = gproc_pool:pick_worker(?CM_POOL, ClientId),
|
||||||
|
gen_server:cast(CmPid, {unregister, ClientId, self()}).
|
||||||
|
|
||||||
|
%%%=============================================================================
|
||||||
|
%%% gen_server callbacks
|
||||||
|
%%%=============================================================================
|
||||||
|
|
||||||
|
init([Id, StatsFun]) ->
|
||||||
|
gproc_pool:connect_worker(?CM_POOL, {?MODULE, Id}),
|
||||||
|
{ok, #state{id = Id, statsfun = StatsFun}}.
|
||||||
|
|
||||||
|
handle_call(Req, _From, State) ->
|
||||||
|
lager:error("unexpected request: ~p", [Req]),
|
||||||
|
{reply, {error, badreq}, State}.
|
||||||
|
|
||||||
|
handle_cast({register, Client = #mqtt_client{client_id = ClientId, client_pid = Pid}}, State) ->
|
||||||
|
lager:info("CM register ~s with ~p", [ClientId, Pid]),
|
||||||
|
case ets:lookup(mqtt_client, ClientId) of
|
||||||
|
[#mqtt_client{client_pid = Pid}] ->
|
||||||
|
lager:error("ClientId '~s' has been registered with ~p", [ClientId, Pid]),
|
||||||
|
ignore;
|
||||||
|
[#mqtt_client{client_pid = OldPid}] ->
|
||||||
|
lager:error("ClientId '~s' is duplicated: pid=~p, oldpid=~p", [ClientId, Pid, OldPid]);
|
||||||
|
[] ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
ets:insert(mqtt_client, Client),
|
||||||
|
{noreply, setstats(State)};
|
||||||
|
|
||||||
|
handle_cast({unregister, ClientId, Pid}, State) ->
|
||||||
|
lager:info("CM unregister ~s with ~p", [ClientId, Pid]),
|
||||||
|
case ets:lookup(mqtt_client, ClientId) of
|
||||||
|
[#mqtt_client{client_pid = Pid}] ->
|
||||||
|
ets:delete(mqtt_client, ClientId);
|
||||||
|
[_] ->
|
||||||
|
ignore;
|
||||||
|
[] ->
|
||||||
|
lager:error("cannot find clientId '~s' with ~p", [ClientId, Pid])
|
||||||
|
end,
|
||||||
|
{noreply, setstats(State)};
|
||||||
|
|
||||||
|
handle_cast(_Msg, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
handle_info(_Info, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
terminate(_Reason, #state{id = Id}) ->
|
||||||
|
gproc_pool:disconnect_worker(?CM_POOL, {?MODULE, Id}), ok.
|
||||||
|
|
||||||
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
|
%%%=============================================================================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%=============================================================================
|
||||||
|
|
||||||
|
setstats(State = #state{statsfun = StatsFun}) ->
|
||||||
|
StatsFun(ets:info(mqtt_client, size)), State.
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
%%%-----------------------------------------------------------------------------
|
||||||
|
%%% Copyright (c) 2012-2015 eMQTT.IO, All Rights Reserved.
|
||||||
|
%%%
|
||||||
|
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
%%% of this software and associated documentation files (the "Software"), to deal
|
||||||
|
%%% in the Software without restriction, including without limitation the rights
|
||||||
|
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
%%% copies of the Software, and to permit persons to whom the Software is
|
||||||
|
%%% furnished to do so, subject to the following conditions:
|
||||||
|
%%%
|
||||||
|
%%% The above copyright notice and this permission notice shall be included in all
|
||||||
|
%%% copies or substantial portions of the Software.
|
||||||
|
%%%
|
||||||
|
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
%%% SOFTWARE.
|
||||||
|
%%%-----------------------------------------------------------------------------
|
||||||
|
%%% @doc
|
||||||
|
%%% emqttd client manager supervisor.
|
||||||
|
%%%
|
||||||
|
%%% @end
|
||||||
|
%%%-----------------------------------------------------------------------------
|
||||||
|
-module(emqttd_cm_sup).
|
||||||
|
|
||||||
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
||||||
|
-include("emqttd.hrl").
|
||||||
|
|
||||||
|
-behaviour(supervisor).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([start_link/0]).
|
||||||
|
|
||||||
|
%% Supervisor callbacks
|
||||||
|
-export([init/1]).
|
||||||
|
|
||||||
|
start_link() ->
|
||||||
|
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||||
|
|
||||||
|
init([]) ->
|
||||||
|
ets:new(mqtt_client, [ordered_set, named_table, public,
|
||||||
|
{keypos, 2}, {write_concurrency, true}]),
|
||||||
|
Schedulers = erlang:system_info(schedulers),
|
||||||
|
gproc_pool:new(emqttd_cm:pool(), hash, [{size, Schedulers}]),
|
||||||
|
StatsFun = emqttd_stats:statsfun('clients/count', 'clients/max'),
|
||||||
|
Children = lists:map(
|
||||||
|
fun(I) ->
|
||||||
|
Name = {emqttd_cm, I},
|
||||||
|
gproc_pool:add_worker(emqttd_cm:pool(), Name, I),
|
||||||
|
{Name, {emqttd_cm, start_link, [I, StatsFun]},
|
||||||
|
permanent, 10000, worker, [emqttd_cm]}
|
||||||
|
end, lists:seq(1, Schedulers)),
|
||||||
|
{ok, {{one_for_all, 10, 100}, Children}}.
|
||||||
|
|
||||||
|
|
|
@ -31,14 +31,14 @@
|
||||||
%%% 1. Timestamp: erlang:system_time if Erlang >= R18, otherwise os:timestamp
|
%%% 1. Timestamp: erlang:system_time if Erlang >= R18, otherwise os:timestamp
|
||||||
%%% 2. NodeId: encode node() to 2 bytes integer
|
%%% 2. NodeId: encode node() to 2 bytes integer
|
||||||
%%% 3. Pid: encode pid to 4 bytes integer
|
%%% 3. Pid: encode pid to 4 bytes integer
|
||||||
%%% 4. Sequence: 2 bytes sequence per pid
|
%%% 4. Sequence: 2 bytes sequence in one process
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_guid).
|
-module(emqttd_guid).
|
||||||
|
|
||||||
-export([gen/0]).
|
-export([gen/0, new/0]).
|
||||||
|
|
||||||
-define(MAX_SEQ, 16#FFFF).
|
-define(MAX_SEQ, 16#FFFF).
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_http).
|
-module(emqttd_http).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
@ -46,13 +47,14 @@ handle_request('POST', "/mqtt/publish", Req) ->
|
||||||
lager:info("HTTP Publish: ~p", [Params]),
|
lager:info("HTTP Publish: ~p", [Params]),
|
||||||
case authorized(Req) of
|
case authorized(Req) of
|
||||||
true ->
|
true ->
|
||||||
|
ClientId = get_value("client", Params, http),
|
||||||
Qos = int(get_value("qos", Params, "0")),
|
Qos = int(get_value("qos", Params, "0")),
|
||||||
Retain = bool(get_value("retain", Params, "0")),
|
Retain = bool(get_value("retain", Params, "0")),
|
||||||
Topic = list_to_binary(get_value("topic", Params)),
|
Topic = list_to_binary(get_value("topic", Params)),
|
||||||
Payload = list_to_binary(get_value("message", Params)),
|
Payload = list_to_binary(get_value("message", Params)),
|
||||||
case {validate(qos, Qos), validate(topic, Topic)} of
|
case {validate(qos, Qos), validate(topic, Topic)} of
|
||||||
{true, true} ->
|
{true, true} ->
|
||||||
Msg = emqttd_message:make(http, Qos, Topic, Payload),
|
Msg = emqttd_message:make(ClientId, Qos, Topic, Payload),
|
||||||
emqttd_pubsub:publish(Msg#mqtt_message{retain = Retain}),
|
emqttd_pubsub:publish(Msg#mqtt_message{retain = Retain}),
|
||||||
Req:ok({"text/plan", <<"ok\n">>});
|
Req:ok({"text/plan", <<"ok\n">>});
|
||||||
{false, _} ->
|
{false, _} ->
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_message).
|
-module(emqttd_message).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
@ -150,7 +151,6 @@ set_flag(retain, Msg = #mqtt_message{retain = false}) ->
|
||||||
Msg#mqtt_message{retain = true};
|
Msg#mqtt_message{retain = true};
|
||||||
set_flag(Flag, Msg) when Flag =:= dup orelse Flag =:= retain -> Msg.
|
set_flag(Flag, Msg) when Flag =:= dup orelse Flag =:= retain -> Msg.
|
||||||
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Unset dup, retain flag
|
%% @doc Unset dup, retain flag
|
||||||
%% @end
|
%% @end
|
||||||
|
@ -170,7 +170,14 @@ unset_flag(Flag, Msg) when Flag =:= dup orelse Flag =:= retain -> Msg.
|
||||||
%% @doc Format MQTT Message
|
%% @doc Format MQTT Message
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
format(#mqtt_message{msgid=MsgId, pktid = PktId, from = From, qos=Qos, retain=Retain, dup=Dup, topic=Topic}) ->
|
format(#mqtt_message{msgid=MsgId,
|
||||||
io_lib:format("Message(MsgId=~p, PktId=~p, from=~s, Qos=~p, Retain=~s, Dup=~s, Topic=~s)",
|
pktid = PktId,
|
||||||
[MsgId, PktId, From, Qos, Retain, Dup, Topic]).
|
from = From,
|
||||||
|
qos=Qos,
|
||||||
|
retain=Retain,
|
||||||
|
dup=Dup,
|
||||||
|
topic=Topic}) ->
|
||||||
|
io_lib:format("Message(MsgId=~p, PktId=~p, from=~s, "
|
||||||
|
"Qos=~p, Retain=~s, Dup=~s, Topic=~s)",
|
||||||
|
[MsgId, PktId, From, Qos, Retain, Dup, Topic]).
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,6 @@
|
||||||
{counter, 'messages/received'}, % Messages received
|
{counter, 'messages/received'}, % Messages received
|
||||||
{counter, 'messages/sent'}, % Messages sent
|
{counter, 'messages/sent'}, % Messages sent
|
||||||
{gauge, 'messages/retained'}, % Messagea retained
|
{gauge, 'messages/retained'}, % Messagea retained
|
||||||
{gauge, 'messages/stored/count'}, % Messages stored
|
|
||||||
{counter, 'messages/dropped'} % Messages dropped
|
{counter, 'messages/dropped'} % Messages dropped
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
@ -236,4 +235,3 @@ create_metric({counter, Name}) ->
|
||||||
metric_topic(Metric) ->
|
metric_topic(Metric) ->
|
||||||
emqttd_topic:systop(list_to_binary(lists:concat(['metrics/', Metric]))).
|
emqttd_topic:systop(list_to_binary(lists:concat(['metrics/', Metric]))).
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_mnesia).
|
-module(emqttd_mnesia).
|
||||||
|
|
||||||
-author('feng@emqtt.io').
|
-author('feng@emqtt.io').
|
||||||
|
|
|
@ -24,12 +24,15 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_mod_presence).
|
-module(emqttd_mod_presence).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
-include("emqttd.hrl").
|
||||||
|
|
||||||
|
-behaviour(emqttd_gen_mod).
|
||||||
|
|
||||||
-export([load/1, unload/1]).
|
-export([load/1, unload/1]).
|
||||||
|
|
||||||
-export([client_connected/3, client_disconnected/3]).
|
-export([client_connected/3, client_disconnected/3]).
|
||||||
|
@ -41,7 +44,7 @@ load(Opts) ->
|
||||||
|
|
||||||
client_connected(ConnAck, #mqtt_client{client_id = ClientId,
|
client_connected(ConnAck, #mqtt_client{client_id = ClientId,
|
||||||
username = Username,
|
username = Username,
|
||||||
ipaddress = IpAddress,
|
peername = {IpAddress, _},
|
||||||
clean_sess = CleanSess,
|
clean_sess = CleanSess,
|
||||||
proto_ver = ProtoVer}, Opts) ->
|
proto_ver = ProtoVer}, Opts) ->
|
||||||
Sess = case CleanSess of
|
Sess = case CleanSess of
|
||||||
|
@ -80,7 +83,8 @@ topic(connected, ClientId) ->
|
||||||
topic(disconnected, ClientId) ->
|
topic(disconnected, ClientId) ->
|
||||||
emqttd_topic:systop(list_to_binary(["clients/", ClientId, "/disconnected"])).
|
emqttd_topic:systop(list_to_binary(["clients/", ClientId, "/disconnected"])).
|
||||||
|
|
||||||
reason(Reason) when is_atom(Reason) -> Reason;
|
reason(Reason) when is_atom(Reason) -> Reason;
|
||||||
reason({Error, _}) when is_atom(Error) -> Error;
|
reason({Error, _}) when is_atom(Error) -> Error;
|
||||||
reason(_) -> internal_error.
|
reason(_) -> internal_error.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ load(Opts) ->
|
||||||
{?MODULE, rewrite, [subscribe, Sections]}),
|
{?MODULE, rewrite, [subscribe, Sections]}),
|
||||||
emqttd_broker:hook('client.unsubscribe', {?MODULE, rewrite_unsubscribe},
|
emqttd_broker:hook('client.unsubscribe', {?MODULE, rewrite_unsubscribe},
|
||||||
{?MODULE, rewrite, [unsubscribe, Sections]}),
|
{?MODULE, rewrite, [unsubscribe, Sections]}),
|
||||||
emqttd_broker:hook('client.publish', {?MODULE, rewrite_publish},
|
emqttd_broker:hook('message.publish', {?MODULE, rewrite_publish},
|
||||||
{?MODULE, rewrite, [publish, Sections]}).
|
{?MODULE, rewrite, [publish, Sections]}).
|
||||||
|
|
||||||
rewrite(_ClientId, TopicTable, subscribe, Sections) ->
|
rewrite(_ClientId, TopicTable, subscribe, Sections) ->
|
||||||
|
@ -85,7 +85,7 @@ reload(File) ->
|
||||||
unload(_) ->
|
unload(_) ->
|
||||||
emqttd_broker:unhook('client.subscribe', {?MODULE, rewrite_subscribe}),
|
emqttd_broker:unhook('client.subscribe', {?MODULE, rewrite_subscribe}),
|
||||||
emqttd_broker:unhook('client.unsubscribe', {?MODULE, rewrite_unsubscribe}),
|
emqttd_broker:unhook('client.unsubscribe', {?MODULE, rewrite_unsubscribe}),
|
||||||
emqttd_broker:unhook('client.publish', {?MODULE, rewrite_publish}).
|
emqttd_broker:unhook('message.publish', {?MODULE, rewrite_publish}).
|
||||||
|
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
%%% Internal functions
|
%%% Internal functions
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_mod_sup).
|
-module(emqttd_mod_sup).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_packet).
|
-module(emqttd_packet).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
|
@ -33,21 +33,25 @@
|
||||||
-include("emqttd_protocol.hrl").
|
-include("emqttd_protocol.hrl").
|
||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([init/1, parse/2]).
|
-export([new/1, parse/2]).
|
||||||
|
|
||||||
-record(mqtt_packet_limit, {max_packet_size}).
|
-record(mqtt_packet_limit, {max_packet_size}).
|
||||||
|
|
||||||
-type option() :: {atom(), any()}.
|
-type option() :: {atom(), any()}.
|
||||||
|
|
||||||
|
-type parser() :: fun( (binary()) -> any() ).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Initialize a parser
|
%% @doc Initialize a parser
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec init(Opts :: [option()]) -> {none, #mqtt_packet_limit{}}.
|
-spec new(Opts :: [option()]) -> parser().
|
||||||
init(Opts) -> {none, limit(Opts)}.
|
new(Opts) ->
|
||||||
|
fun(Bin) -> parse(Bin, {none, limit(Opts)}) end.
|
||||||
|
|
||||||
limit(Opts) ->
|
limit(Opts) ->
|
||||||
#mqtt_packet_limit{max_packet_size = proplists:get_value(max_packet_size, Opts, ?MAX_LEN)}.
|
#mqtt_packet_limit{max_packet_size =
|
||||||
|
proplists:get_value(max_packet_size, Opts, ?MAX_LEN)}.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Parse MQTT Packet
|
%% @doc Parse MQTT Packet
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_pooler_sup).
|
-module(emqttd_pooler_sup).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_protocol).
|
-module(emqttd_protocol).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
@ -40,20 +41,20 @@
|
||||||
-export([handle/2]).
|
-export([handle/2]).
|
||||||
|
|
||||||
%% Protocol State
|
%% Protocol State
|
||||||
-record(proto_state, {
|
-record(proto_state, {peername,
|
||||||
peername,
|
sendfun,
|
||||||
sendfun,
|
connected = false, %received CONNECT action?
|
||||||
connected = false, %received CONNECT action?
|
proto_ver,
|
||||||
proto_ver,
|
proto_name,
|
||||||
proto_name,
|
username,
|
||||||
username,
|
client_id,
|
||||||
client_id,
|
clean_sess,
|
||||||
clean_sess,
|
session,
|
||||||
session,
|
will_msg,
|
||||||
will_msg,
|
keepalive,
|
||||||
max_clientid_len = ?MAX_CLIENTID_LEN,
|
max_clientid_len = ?MAX_CLIENTID_LEN,
|
||||||
client_pid
|
client_pid,
|
||||||
}).
|
connected_at}).
|
||||||
|
|
||||||
-type proto_state() :: #proto_state{}.
|
-type proto_state() :: #proto_state{}.
|
||||||
|
|
||||||
|
@ -61,6 +62,7 @@
|
||||||
%% @doc Init protocol
|
%% @doc Init protocol
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
init(Peername, SendFun, Opts) ->
|
init(Peername, SendFun, Opts) ->
|
||||||
MaxLen = proplists:get_value(max_clientid_len, Opts, ?MAX_CLIENTID_LEN),
|
MaxLen = proplists:get_value(max_clientid_len, Opts, ?MAX_CLIENTID_LEN),
|
||||||
#proto_state{peername = Peername,
|
#proto_state{peername = Peername,
|
||||||
|
@ -68,32 +70,50 @@ init(Peername, SendFun, Opts) ->
|
||||||
max_clientid_len = MaxLen,
|
max_clientid_len = MaxLen,
|
||||||
client_pid = self()}.
|
client_pid = self()}.
|
||||||
|
|
||||||
info(#proto_state{proto_ver = ProtoVer,
|
info(#proto_state{client_id = ClientId,
|
||||||
|
username = Username,
|
||||||
|
peername = Peername,
|
||||||
|
proto_ver = ProtoVer,
|
||||||
proto_name = ProtoName,
|
proto_name = ProtoName,
|
||||||
client_id = ClientId,
|
keepalive = KeepAlive,
|
||||||
clean_sess = CleanSess,
|
clean_sess = CleanSess,
|
||||||
will_msg = WillMsg}) ->
|
will_msg = WillMsg,
|
||||||
[{proto_ver, ProtoVer},
|
connected_at = ConnectedAt}) ->
|
||||||
|
[{client_id, ClientId},
|
||||||
|
{username, Username},
|
||||||
|
{peername, Peername},
|
||||||
|
{proto_ver, ProtoVer},
|
||||||
{proto_name, ProtoName},
|
{proto_name, ProtoName},
|
||||||
{client_id, ClientId},
|
{keepalive, KeepAlive},
|
||||||
{clean_sess, CleanSess},
|
{clean_sess, CleanSess},
|
||||||
{will_msg, WillMsg}].
|
{will_msg, WillMsg},
|
||||||
|
{connected_at, ConnectedAt}].
|
||||||
|
|
||||||
clientid(#proto_state{client_id = ClientId}) ->
|
clientid(#proto_state{client_id = ClientId}) ->
|
||||||
ClientId.
|
ClientId.
|
||||||
|
|
||||||
client(#proto_state{peername = {Addr, _Port},
|
client(#proto_state{client_id = ClientId,
|
||||||
client_id = ClientId,
|
peername = Peername,
|
||||||
username = Username,
|
username = Username,
|
||||||
clean_sess = CleanSess,
|
clean_sess = CleanSess,
|
||||||
proto_ver = ProtoVer,
|
proto_ver = ProtoVer,
|
||||||
client_pid = Pid}) ->
|
keepalive = Keepalive,
|
||||||
#mqtt_client{client_id = ClientId,
|
will_msg = WillMsg,
|
||||||
|
client_pid = Pid,
|
||||||
|
connected_at = Time}) ->
|
||||||
|
WillTopic = if
|
||||||
|
WillMsg =:= undefined -> undefined;
|
||||||
|
true -> WillMsg#mqtt_message.topic
|
||||||
|
end,
|
||||||
|
#mqtt_client{client_id = ClientId,
|
||||||
|
client_pid = Pid,
|
||||||
username = Username,
|
username = Username,
|
||||||
ipaddress = Addr,
|
peername = Peername,
|
||||||
clean_sess = CleanSess,
|
clean_sess = CleanSess,
|
||||||
proto_ver = ProtoVer,
|
proto_ver = ProtoVer,
|
||||||
client_pid = Pid}.
|
keepalive = Keepalive,
|
||||||
|
will_topic = WillTopic,
|
||||||
|
connected_at = Time}.
|
||||||
|
|
||||||
%% CONNECT – Client requests a connection to a Server
|
%% CONNECT – Client requests a connection to a Server
|
||||||
|
|
||||||
|
@ -111,7 +131,7 @@ received(_Packet, State = #proto_state{connected = false}) ->
|
||||||
|
|
||||||
received(Packet = ?PACKET(_Type), State) ->
|
received(Packet = ?PACKET(_Type), State) ->
|
||||||
trace(recv, Packet, State),
|
trace(recv, Packet, State),
|
||||||
case validate_packet(Packet) of
|
case validate_packet(Packet) of
|
||||||
ok ->
|
ok ->
|
||||||
handle(Packet, State);
|
handle(Packet, State);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
|
@ -121,18 +141,21 @@ received(Packet = ?PACKET(_Type), State) ->
|
||||||
handle(Packet = ?CONNECT_PACKET(Var), State0 = #proto_state{peername = Peername}) ->
|
handle(Packet = ?CONNECT_PACKET(Var), State0 = #proto_state{peername = Peername}) ->
|
||||||
|
|
||||||
#mqtt_packet_connect{proto_ver = ProtoVer,
|
#mqtt_packet_connect{proto_ver = ProtoVer,
|
||||||
proto_name = ProtoName,
|
proto_name = ProtoName,
|
||||||
username = Username,
|
username = Username,
|
||||||
password = Password,
|
password = Password,
|
||||||
clean_sess = CleanSess,
|
clean_sess = CleanSess,
|
||||||
keep_alive = KeepAlive,
|
keep_alive = KeepAlive,
|
||||||
client_id = ClientId} = Var,
|
client_id = ClientId} = Var,
|
||||||
|
|
||||||
State1 = State0#proto_state{proto_ver = ProtoVer,
|
State1 = State0#proto_state{proto_ver = ProtoVer,
|
||||||
proto_name = ProtoName,
|
proto_name = ProtoName,
|
||||||
username = Username,
|
username = Username,
|
||||||
client_id = ClientId,
|
client_id = ClientId,
|
||||||
clean_sess = CleanSess},
|
clean_sess = CleanSess,
|
||||||
|
keepalive = KeepAlive,
|
||||||
|
will_msg = willmsg(Var),
|
||||||
|
connected_at = os:timestamp()},
|
||||||
|
|
||||||
trace(recv, Packet, State1),
|
trace(recv, Packet, State1),
|
||||||
|
|
||||||
|
@ -142,16 +165,20 @@ handle(Packet = ?CONNECT_PACKET(Var), State0 = #proto_state{peername = Peername}
|
||||||
case emqttd_access_control:auth(client(State1), Password) of
|
case emqttd_access_control:auth(client(State1), Password) of
|
||||||
ok ->
|
ok ->
|
||||||
%% Generate clientId if null
|
%% Generate clientId if null
|
||||||
State2 = State1#proto_state{client_id = clientid(ClientId, State1)},
|
State2 = maybe_set_clientid(State1),
|
||||||
|
|
||||||
%%Starting session
|
%% Start session
|
||||||
{ok, Session} = emqttd_sm:start_session(CleanSess, clientid(State2)),
|
case emqttd_sm:start_session(CleanSess, clientid(State2)) of
|
||||||
|
{ok, Session} ->
|
||||||
%% Start keepalive
|
%% Register the client
|
||||||
start_keepalive(KeepAlive),
|
emqttd_cm:register(client(State2)),
|
||||||
|
%% Start keepalive
|
||||||
%% ACCEPT
|
start_keepalive(KeepAlive),
|
||||||
{?CONNACK_ACCEPT, State2#proto_state{session = Session, will_msg = willmsg(Var)}};
|
%% ACCEPT
|
||||||
|
{?CONNACK_ACCEPT, State2#proto_state{session = Session}};
|
||||||
|
{error, Error} ->
|
||||||
|
exit({shutdown, Error})
|
||||||
|
end;
|
||||||
{error, Reason}->
|
{error, Reason}->
|
||||||
lager:error("~s@~s: username '~s', login failed - ~s",
|
lager:error("~s@~s: username '~s', login failed - ~s",
|
||||||
[ClientId, emqttd_net:format(Peername), Username, Reason]),
|
[ClientId, emqttd_net:format(Peername), Username, Reason]),
|
||||||
|
@ -177,8 +204,6 @@ handle(Packet = ?PUBLISH_PACKET(_Qos, Topic, _PacketId, _Payload),
|
||||||
end,
|
end,
|
||||||
{ok, State};
|
{ok, State};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
handle(?PUBACK_PACKET(?PUBACK, PacketId), State = #proto_state{session = Session}) ->
|
handle(?PUBACK_PACKET(?PUBACK, PacketId), State = #proto_state{session = Session}) ->
|
||||||
emqttd_session:puback(Session, PacketId),
|
emqttd_session:puback(Session, PacketId),
|
||||||
{ok, State};
|
{ok, State};
|
||||||
|
@ -239,8 +264,7 @@ publish(Packet = ?PUBLISH(?QOS_1, PacketId), State = #proto_state{client_id = Cl
|
||||||
ok ->
|
ok ->
|
||||||
send(?PUBACK_PACKET(?PUBACK, PacketId), State);
|
send(?PUBACK_PACKET(?PUBACK, PacketId), State);
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
%%TODO: log format...
|
lager:error("Client ~s: publish qos1 error - ~p", [ClientId, Error])
|
||||||
lager:error("Client ~s: publish qos1 error ~p", [ClientId, Error])
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
publish(Packet = ?PUBLISH(?QOS_2, PacketId), State = #proto_state{client_id = ClientId, session = Session}) ->
|
publish(Packet = ?PUBLISH(?QOS_2, PacketId), State = #proto_state{client_id = ClientId, session = Session}) ->
|
||||||
|
@ -248,15 +272,15 @@ publish(Packet = ?PUBLISH(?QOS_2, PacketId), State = #proto_state{client_id = Cl
|
||||||
ok ->
|
ok ->
|
||||||
send(?PUBACK_PACKET(?PUBREC, PacketId), State);
|
send(?PUBACK_PACKET(?PUBREC, PacketId), State);
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
%%TODO: log format...
|
lager:error("Client ~s: publish qos2 error - ~p", [ClientId, Error])
|
||||||
lager:error("Client ~s: publish qos2 error ~p", [ClientId, Error])
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec send(mqtt_message() | mqtt_packet(), proto_state()) -> {ok, proto_state()}.
|
-spec send(mqtt_message() | mqtt_packet(), proto_state()) -> {ok, proto_state()}.
|
||||||
send(Msg, State) when is_record(Msg, mqtt_message) ->
|
send(Msg, State) when is_record(Msg, mqtt_message) ->
|
||||||
send(emqttd_message:to_packet(Msg), State);
|
send(emqttd_message:to_packet(Msg), State);
|
||||||
|
|
||||||
send(Packet, State = #proto_state{sendfun = SendFun, peername = Peername}) when is_record(Packet, mqtt_packet) ->
|
send(Packet, State = #proto_state{sendfun = SendFun, peername = Peername})
|
||||||
|
when is_record(Packet, mqtt_packet) ->
|
||||||
trace(send, Packet, State),
|
trace(send, Packet, State),
|
||||||
sent_stats(Packet),
|
sent_stats(Packet),
|
||||||
Data = emqttd_serialiser:serialise(Packet),
|
Data = emqttd_serialiser:serialise(Packet),
|
||||||
|
@ -280,26 +304,29 @@ redeliver({?PUBREL, PacketId}, State) ->
|
||||||
shutdown(duplicate_id, _State) ->
|
shutdown(duplicate_id, _State) ->
|
||||||
quiet; %%
|
quiet; %%
|
||||||
|
|
||||||
shutdown(_, #proto_state{client_id = undefined}) ->
|
shutdown(Error, #proto_state{client_id = undefined}) ->
|
||||||
|
lager:info("Protocol shutdown ~p", [Error]),
|
||||||
ignore;
|
ignore;
|
||||||
|
|
||||||
shutdown(Error, #proto_state{peername = Peername, client_id = ClientId, will_msg = WillMsg}) ->
|
shutdown(Error, #proto_state{peername = Peername, client_id = ClientId, will_msg = WillMsg}) ->
|
||||||
lager:info([{client, ClientId}], "Client ~s@~s: shutdown ~p",
|
lager:info([{client, ClientId}], "Client ~s@~s: shutdown ~p",
|
||||||
[ClientId, emqttd_net:format(Peername), Error]),
|
[ClientId, emqttd_net:format(Peername), Error]),
|
||||||
send_willmsg(ClientId, WillMsg),
|
send_willmsg(ClientId, WillMsg),
|
||||||
|
emqttd_cm:unregister(ClientId),
|
||||||
emqttd_broker:foreach_hooks('client.disconnected', [Error, ClientId]).
|
emqttd_broker:foreach_hooks('client.disconnected', [Error, ClientId]).
|
||||||
|
|
||||||
willmsg(Packet) when is_record(Packet, mqtt_packet_connect) ->
|
willmsg(Packet) when is_record(Packet, mqtt_packet_connect) ->
|
||||||
emqttd_message:from_packet(Packet).
|
emqttd_message:from_packet(Packet).
|
||||||
|
|
||||||
%% generate a clientId
|
%% Generate a client if if nulll
|
||||||
clientid(undefined, State) ->
|
maybe_set_clientid(State = #proto_state{client_id = NullId})
|
||||||
clientid(<<>>, State);
|
when NullId =:= undefined orelse NullId =:= <<>> ->
|
||||||
%%TODO: <<>> is not right.
|
{_, NPid, _} = emqttd_guid:new(),
|
||||||
clientid(<<>>, #proto_state{peername = Peername}) ->
|
ClientId = iolist_to_binary(["emqttd_", integer_to_list(NPid)]),
|
||||||
{_, _, MicroSecs} = os:timestamp(),
|
State#proto_state{client_id = ClientId};
|
||||||
iolist_to_binary(["emqttd_", base64:encode(emqttd_net:format(Peername)), integer_to_list(MicroSecs)]);
|
|
||||||
clientid(ClientId, _State) -> ClientId.
|
maybe_set_clientid(State) ->
|
||||||
|
State.
|
||||||
|
|
||||||
send_willmsg(_ClientId, undefined) ->
|
send_willmsg(_ClientId, undefined) ->
|
||||||
ignore;
|
ignore;
|
||||||
|
|
|
@ -39,17 +39,19 @@
|
||||||
-boot_mnesia({mnesia, [boot]}).
|
-boot_mnesia({mnesia, [boot]}).
|
||||||
-copy_mnesia({mnesia, [copy]}).
|
-copy_mnesia({mnesia, [copy]}).
|
||||||
|
|
||||||
-behaviour(gen_server).
|
|
||||||
|
|
||||||
%% API Exports
|
%% API Exports
|
||||||
-export([start_link/2]).
|
-export([start_link/2]).
|
||||||
|
|
||||||
-export([create/1,
|
-export([create/1,
|
||||||
subscribe/1,
|
subscribe/1,
|
||||||
unsubscribe/1,
|
unsubscribe/1,
|
||||||
publish/1,
|
publish/1]).
|
||||||
%local node
|
|
||||||
dispatch/2, match/1]).
|
%% Local node
|
||||||
|
-export([dispatch/2,
|
||||||
|
match/1]).
|
||||||
|
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
%% gen_server Function Exports
|
%% gen_server Function Exports
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
|
@ -62,6 +64,7 @@
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
%%% Mnesia callbacks
|
%%% Mnesia callbacks
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
|
|
||||||
mnesia(boot) ->
|
mnesia(boot) ->
|
||||||
%% p2p queue table
|
%% p2p queue table
|
||||||
ok = emqttd_mnesia:create_table(queue, [
|
ok = emqttd_mnesia:create_table(queue, [
|
||||||
|
@ -111,6 +114,7 @@ start_link(Id, Opts) ->
|
||||||
create(<<"$Q/", _Queue/binary>>) ->
|
create(<<"$Q/", _Queue/binary>>) ->
|
||||||
%% protecte from queue
|
%% protecte from queue
|
||||||
{error, cannot_create_queue};
|
{error, cannot_create_queue};
|
||||||
|
|
||||||
create(Topic) when is_binary(Topic) ->
|
create(Topic) when is_binary(Topic) ->
|
||||||
TopicR = #mqtt_topic{topic = Topic, node = node()},
|
TopicR = #mqtt_topic{topic = Topic, node = node()},
|
||||||
case mnesia:transaction(fun add_topic/1, [TopicR]) of
|
case mnesia:transaction(fun add_topic/1, [TopicR]) of
|
||||||
|
@ -124,7 +128,8 @@ create(Topic) when is_binary(Topic) ->
|
||||||
%% @doc Subscribe topic
|
%% @doc Subscribe topic
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec subscribe({Topic, Qos} | list({Topic, Qos})) -> {ok, Qos | list(Qos)} | {error, any()} when
|
-spec subscribe({Topic, Qos} | list({Topic, Qos})) ->
|
||||||
|
{ok, Qos | list(Qos)} | {error, any()} when
|
||||||
Topic :: binary(),
|
Topic :: binary(),
|
||||||
Qos :: mqtt_qos().
|
Qos :: mqtt_qos().
|
||||||
subscribe({Topic, Qos}) when is_binary(Topic) andalso ?IS_QOS(Qos) ->
|
subscribe({Topic, Qos}) when is_binary(Topic) andalso ?IS_QOS(Qos) ->
|
||||||
|
@ -158,15 +163,14 @@ cast(Msg) ->
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec publish(Msg :: mqtt_message()) -> ok.
|
-spec publish(Msg :: mqtt_message()) -> ok.
|
||||||
publish(#mqtt_message{from = From} = Msg) ->
|
publish(#mqtt_message{from = From} = Msg) ->
|
||||||
|
|
||||||
trace(publish, From, Msg),
|
trace(publish, From, Msg),
|
||||||
|
Msg1 = #mqtt_message{topic = Topic}
|
||||||
Msg1 = #mqtt_message{topic = Topic} = emqttd_broker:foldl_hooks('client.publish', [], Msg),
|
= emqttd_broker:foldl_hooks('message.publish', [], Msg),
|
||||||
|
|
||||||
%% Retain message first. Don't create retained topic.
|
%% Retain message first. Don't create retained topic.
|
||||||
case emqttd_retained:retain(Msg1) of
|
case emqttd_retained:retain(Msg1) of
|
||||||
ok ->
|
ok ->
|
||||||
%TODO: why unset 'retain' flag?
|
%% TODO: why unset 'retain' flag?
|
||||||
publish(Topic, emqttd_message:unset_flag(Msg1));
|
publish(Topic, emqttd_message:unset_flag(Msg1));
|
||||||
ignore ->
|
ignore ->
|
||||||
publish(Topic, Msg1)
|
publish(Topic, Msg1)
|
||||||
|
@ -174,12 +178,12 @@ publish(#mqtt_message{from = From} = Msg) ->
|
||||||
|
|
||||||
publish(<<"$Q/", _/binary>> = Queue, #mqtt_message{qos = Qos} = Msg) ->
|
publish(<<"$Q/", _/binary>> = Queue, #mqtt_message{qos = Qos} = Msg) ->
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(#mqtt_queue{subpid = SubPid, qos = SubQos}) ->
|
fun(#mqtt_queue{qpid = QPid, qos = SubQos}) ->
|
||||||
Msg1 = if
|
Msg1 = if
|
||||||
Qos > SubQos -> Msg#mqtt_message{qos = SubQos};
|
Qos > SubQos -> Msg#mqtt_message{qos = SubQos};
|
||||||
true -> Msg
|
true -> Msg
|
||||||
end,
|
end,
|
||||||
SubPid ! {dispatch, Msg1}
|
QPid ! {dispatch, Msg1}
|
||||||
end, mnesia:dirty_read(queue, Queue));
|
end, mnesia:dirty_read(queue, Queue));
|
||||||
|
|
||||||
publish(Topic, Msg) when is_binary(Topic) ->
|
publish(Topic, Msg) when is_binary(Topic) ->
|
||||||
|
@ -197,7 +201,7 @@ publish(Topic, Msg) when is_binary(Topic) ->
|
||||||
-spec dispatch(Topic :: binary(), Msg :: mqtt_message()) -> non_neg_integer().
|
-spec dispatch(Topic :: binary(), Msg :: mqtt_message()) -> non_neg_integer().
|
||||||
dispatch(Topic, #mqtt_message{qos = Qos} = Msg ) when is_binary(Topic) ->
|
dispatch(Topic, #mqtt_message{qos = Qos} = Msg ) when is_binary(Topic) ->
|
||||||
Subscribers = mnesia:dirty_read(subscriber, Topic),
|
Subscribers = mnesia:dirty_read(subscriber, Topic),
|
||||||
setstats(dropped, Subscribers =:= []), %%TODO:...
|
setstats(dropped, Subscribers =:= []),
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(#mqtt_subscriber{subpid=SubPid, qos = SubQos}) ->
|
fun(#mqtt_subscriber{subpid=SubPid, qos = SubQos}) ->
|
||||||
Msg1 = if
|
Msg1 = if
|
||||||
|
@ -220,12 +224,11 @@ match(Topic) when is_binary(Topic) ->
|
||||||
init([Id, _Opts]) ->
|
init([Id, _Opts]) ->
|
||||||
process_flag(min_heap_size, 1024*1024),
|
process_flag(min_heap_size, 1024*1024),
|
||||||
gproc_pool:connect_worker(pubsub, {?MODULE, Id}),
|
gproc_pool:connect_worker(pubsub, {?MODULE, Id}),
|
||||||
%%TODO: gb_trees to replace maps?
|
|
||||||
{ok, #state{id = Id, submap = maps:new()}}.
|
{ok, #state{id = Id, submap = maps:new()}}.
|
||||||
|
|
||||||
handle_call({subscribe, SubPid, Topics}, _From, State) ->
|
handle_call({subscribe, SubPid, Topics}, _From, State) ->
|
||||||
TopicSubs = lists:map(fun({<<"$Q/", _/binary>> = Queue, Qos}) ->
|
TopicSubs = lists:map(fun({<<"$Q/", _/binary>> = Queue, Qos}) ->
|
||||||
#mqtt_queue{name = Queue, subpid = SubPid, qos = Qos};
|
#mqtt_queue{name = Queue, qpid = SubPid, qos = Qos};
|
||||||
({Topic, Qos}) ->
|
({Topic, Qos}) ->
|
||||||
{#mqtt_topic{topic = Topic, node = node()},
|
{#mqtt_topic{topic = Topic, node = node()},
|
||||||
#mqtt_subscriber{topic = Topic, subpid = SubPid, qos = Qos}}
|
#mqtt_subscriber{topic = Topic, subpid = SubPid, qos = Qos}}
|
||||||
|
@ -252,7 +255,7 @@ handle_call({subscribe, SubPid, <<"$Q/", _/binary>> = Queue, Qos}, _From, State)
|
||||||
[OldQueueR] -> lager:error("Queue is overwrited by ~p: ~p", [SubPid, OldQueueR]);
|
[OldQueueR] -> lager:error("Queue is overwrited by ~p: ~p", [SubPid, OldQueueR]);
|
||||||
[] -> ok
|
[] -> ok
|
||||||
end,
|
end,
|
||||||
QueueR = #mqtt_queue{name = Queue, subpid = SubPid, qos = Qos},
|
QueueR = #mqtt_queue{name = Queue, qpid = SubPid, qos = Qos},
|
||||||
case mnesia:transaction(fun add_queue/1, [QueueR]) of
|
case mnesia:transaction(fun add_queue/1, [QueueR]) of
|
||||||
{atomic, ok} ->
|
{atomic, ok} ->
|
||||||
setstats(queues),
|
setstats(queues),
|
||||||
|
@ -279,7 +282,7 @@ handle_call(Req, _From, State) ->
|
||||||
handle_cast({unsubscribe, SubPid, Topics}, State) when is_list(Topics) ->
|
handle_cast({unsubscribe, SubPid, Topics}, State) when is_list(Topics) ->
|
||||||
|
|
||||||
TopicSubs = lists:map(fun(<<"$Q/", _/binary>> = Queue) ->
|
TopicSubs = lists:map(fun(<<"$Q/", _/binary>> = Queue) ->
|
||||||
#mqtt_queue{name = Queue, subpid = SubPid};
|
#mqtt_queue{name = Queue, qpid = SubPid};
|
||||||
(Topic) ->
|
(Topic) ->
|
||||||
{#mqtt_topic{topic = Topic, node = node()},
|
{#mqtt_topic{topic = Topic, node = node()},
|
||||||
#mqtt_subscriber{topic = Topic, subpid = SubPid, _ = '_'}}
|
#mqtt_subscriber{topic = Topic, subpid = SubPid, _ = '_'}}
|
||||||
|
@ -300,7 +303,7 @@ handle_cast({unsubscribe, SubPid, Topics}, State) when is_list(Topics) ->
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
|
||||||
handle_cast({unsubscribe, SubPid, <<"$Q/", _/binary>> = Queue}, State) ->
|
handle_cast({unsubscribe, SubPid, <<"$Q/", _/binary>> = Queue}, State) ->
|
||||||
QueueR = #mqtt_queue{name = Queue, subpid = SubPid},
|
QueueR = #mqtt_queue{name = Queue, qpid = SubPid},
|
||||||
case mnesia:transaction(fun remove_queue/1, [QueueR]) of
|
case mnesia:transaction(fun remove_queue/1, [QueueR]) of
|
||||||
{atomic, _} ->
|
{atomic, _} ->
|
||||||
setstats(queues);
|
setstats(queues);
|
||||||
|
@ -329,7 +332,7 @@ handle_info({'DOWN', _Mon, _Type, DownPid, _Info}, State = #state{submap = SubMa
|
||||||
Node = node(),
|
Node = node(),
|
||||||
F = fun() ->
|
F = fun() ->
|
||||||
%% remove queue...
|
%% remove queue...
|
||||||
Queues = mnesia:match_object(queue, #mqtt_queue{subpid = DownPid, _ = '_'}, write),
|
Queues = mnesia:match_object(queue, #mqtt_queue{qpid = DownPid, _ = '_'}, write),
|
||||||
lists:foreach(fun(QueueR) ->
|
lists:foreach(fun(QueueR) ->
|
||||||
mnesia:delete_object(queue, QueueR, write)
|
mnesia:delete_object(queue, QueueR, write)
|
||||||
end, Queues),
|
end, Queues),
|
||||||
|
@ -420,9 +423,9 @@ monitor_subscriber(SubPid, State = #state{submap = SubMap}) ->
|
||||||
end,
|
end,
|
||||||
State#state{submap = NewSubMap}.
|
State#state{submap = NewSubMap}.
|
||||||
|
|
||||||
remove_queue(#mqtt_queue{name = Name, subpid = Pid}) ->
|
remove_queue(#mqtt_queue{name = Name, qpid = Pid}) ->
|
||||||
case mnesia:wread({queue, Name}) of
|
case mnesia:wread({queue, Name}) of
|
||||||
[R = #mqtt_queue{subpid = Pid}] ->
|
[R = #mqtt_queue{qpid = Pid}] ->
|
||||||
mnesia:delete(queue, R, write);
|
mnesia:delete(queue, R, write);
|
||||||
_ ->
|
_ ->
|
||||||
ok
|
ok
|
||||||
|
@ -463,13 +466,11 @@ setstats(subscribers) ->
|
||||||
emqttd_stats:setstats('subscribers/count', 'subscribers/max',
|
emqttd_stats:setstats('subscribers/count', 'subscribers/max',
|
||||||
mnesia:table_info(subscriber, size)).
|
mnesia:table_info(subscriber, size)).
|
||||||
|
|
||||||
%%TODO: queue dropped?
|
|
||||||
setstats(dropped, false) ->
|
setstats(dropped, false) ->
|
||||||
ignore;
|
ignore;
|
||||||
setstats(dropped, true) ->
|
setstats(dropped, true) ->
|
||||||
emqttd_metrics:inc('messages/dropped').
|
emqttd_metrics:inc('messages/dropped').
|
||||||
|
|
||||||
|
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
%%% Trace functions
|
%%% Trace functions
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_pubsub_sup).
|
-module(emqttd_pubsub_sup).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_retained).
|
-module(emqttd_retained).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
@ -73,7 +74,7 @@ retain(#mqtt_message{retain = true, topic = Topic, payload = <<>>}) ->
|
||||||
retain(Msg = #mqtt_message{topic = Topic,
|
retain(Msg = #mqtt_message{topic = Topic,
|
||||||
retain = true,
|
retain = true,
|
||||||
payload = Payload}) ->
|
payload = Payload}) ->
|
||||||
TabSize = mnesia:table_info(message, size),
|
TabSize = mnesia:table_info(retained, size),
|
||||||
case {TabSize < limit(table), size(Payload) < limit(payload)} of
|
case {TabSize < limit(table), size(Payload) < limit(payload)} of
|
||||||
{true, true} ->
|
{true, true} ->
|
||||||
Retained = #mqtt_retained{topic = Topic, message = Msg},
|
Retained = #mqtt_retained{topic = Topic, message = Msg},
|
||||||
|
@ -83,7 +84,7 @@ retain(Msg = #mqtt_message{topic = Topic,
|
||||||
{false, _}->
|
{false, _}->
|
||||||
lager:error("Dropped retained message(topic=~s) for table is full!", [Topic]);
|
lager:error("Dropped retained message(topic=~s) for table is full!", [Topic]);
|
||||||
{_, false}->
|
{_, false}->
|
||||||
lager:error("Dropped retained message(topic=~s, payload=~p) for payload is too big!", [Topic, size(Payload)])
|
lager:error("Dropped retained message(topic=~s, payload_size=~p) for payload is too big!", [Topic, size(Payload)])
|
||||||
end, ok.
|
end, ok.
|
||||||
|
|
||||||
limit(table) ->
|
limit(table) ->
|
||||||
|
@ -107,7 +108,7 @@ env() ->
|
||||||
-spec dispatch(Topic, CPid) -> any() when
|
-spec dispatch(Topic, CPid) -> any() when
|
||||||
Topic :: binary(),
|
Topic :: binary(),
|
||||||
CPid :: pid().
|
CPid :: pid().
|
||||||
dispatch(Topic, CPid) when is_binary(Topic) andalso is_pid(CPid) ->
|
dispatch(Topic, CPid) when is_binary(Topic) ->
|
||||||
Msgs =
|
Msgs =
|
||||||
case emqttd_topic:wildcard(Topic) of
|
case emqttd_topic:wildcard(Topic) of
|
||||||
false ->
|
false ->
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_serialiser).
|
-module(emqttd_serialiser).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
|
@ -22,16 +22,6 @@
|
||||||
%%% @doc
|
%%% @doc
|
||||||
%%% emqttd session manager.
|
%%% emqttd session manager.
|
||||||
%%%
|
%%%
|
||||||
%%% The Session state in the Server consists of:
|
|
||||||
%%% The existence of a Session, even if the rest of the Session state is empty.
|
|
||||||
%%% The Client’s subscriptions.
|
|
||||||
%%% QoS 1 and QoS 2 messages which have been sent to the Client, but have not
|
|
||||||
%%% been completely acknowledged.
|
|
||||||
%%% QoS 1 and QoS 2 messages pending transmission to the Client.
|
|
||||||
%%% QoS 2 messages which have been received from the Client, but have not been
|
|
||||||
%%% completely acknowledged.
|
|
||||||
%%% Optionally, QoS 0 messages pending transmission to the Client.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -41,14 +31,18 @@
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
-include("emqttd.hrl").
|
||||||
|
|
||||||
-behaviour(gen_server).
|
%% Mnesia Callbacks
|
||||||
|
-export([mnesia/1]).
|
||||||
|
|
||||||
|
-boot_mnesia({mnesia, [boot]}).
|
||||||
|
-copy_mnesia({mnesia, [copy]}).
|
||||||
|
|
||||||
%% API Function Exports
|
%% API Function Exports
|
||||||
-export([start_link/2, pool/0, table/0]).
|
-export([start_link/2, pool/0]).
|
||||||
|
|
||||||
-export([lookup_session/1,
|
-export([start_session/2, lookup_session/1]).
|
||||||
start_session/2,
|
|
||||||
destroy_session/1]).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
%% gen_server Function Exports
|
%% gen_server Function Exports
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
|
@ -58,7 +52,20 @@
|
||||||
|
|
||||||
-define(SM_POOL, sm_pool).
|
-define(SM_POOL, sm_pool).
|
||||||
|
|
||||||
-define(SESSION_TAB, mqtt_session).
|
%%%=============================================================================
|
||||||
|
%%% Mnesia callbacks
|
||||||
|
%%%=============================================================================
|
||||||
|
|
||||||
|
mnesia(boot) ->
|
||||||
|
ok = emqttd_mnesia:create_table(session, [
|
||||||
|
{type, ordered_set},
|
||||||
|
{ram_copies, [node()]},
|
||||||
|
{record_name, mqtt_session},
|
||||||
|
{attributes, record_info(fields, mqtt_session)},
|
||||||
|
{index, [sess_pid]}]);
|
||||||
|
|
||||||
|
mnesia(copy) ->
|
||||||
|
ok = emqttd_mnesia:copy_table(session).
|
||||||
|
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
%%% API
|
%%% API
|
||||||
|
@ -69,8 +76,8 @@
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec start_link(Id, StatsFun) -> {ok, pid()} | ignore | {error, any()} when
|
-spec start_link(Id, StatsFun) -> {ok, pid()} | ignore | {error, any()} when
|
||||||
Id :: pos_integer(),
|
Id :: pos_integer(),
|
||||||
StatsFun :: {fun(), fun()}.
|
StatsFun :: fun().
|
||||||
start_link(Id, StatsFun) ->
|
start_link(Id, StatsFun) ->
|
||||||
gen_server:start_link(?MODULE, [Id, StatsFun], []).
|
gen_server:start_link(?MODULE, [Id, StatsFun], []).
|
||||||
|
|
||||||
|
@ -80,43 +87,27 @@ start_link(Id, StatsFun) ->
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
pool() -> ?SM_POOL.
|
pool() -> ?SM_POOL.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Table name.
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
table() -> ?SESSION_TAB.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Start a session
|
%% @doc Start a session
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
-spec start_session(CleanSess :: boolean(), binary()) -> {ok, pid()} | {error, any()}.
|
-spec start_session(CleanSess :: boolean(), binary()) -> {ok, pid()} | {error, any()}.
|
||||||
start_session(CleanSess, ClientId) ->
|
start_session(CleanSess, ClientId) ->
|
||||||
SM = gproc_pool:pick_worker(?SM_POOL, ClientId),
|
SM = gproc_pool:pick_worker(?SM_POOL, ClientId),
|
||||||
call(SM, {start_session, {CleanSess, ClientId, self()}}).
|
call(SM, {start_session, {CleanSess, ClientId, self()}}).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Lookup Session Pid
|
%% @doc Lookup a Session
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec lookup_session(binary()) -> pid() | undefined.
|
-spec lookup_session(binary()) -> pid() | undefined.
|
||||||
lookup_session(ClientId) ->
|
lookup_session(ClientId) ->
|
||||||
case ets:lookup(?SESSION_TAB, ClientId) of
|
case mnesia:dirty_read(session, ClientId) of
|
||||||
[{_Clean, _, SessPid, _}] -> SessPid;
|
[Session] -> Session;
|
||||||
[] -> undefined
|
[] -> undefined
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
call(SM, Req) -> gen_server:call(SM, Req, infinity).
|
||||||
%% @doc Destroy a session
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec destroy_session(binary()) -> ok.
|
|
||||||
destroy_session(ClientId) ->
|
|
||||||
SM = gproc_pool:pick_worker(?SM_POOL, ClientId),
|
|
||||||
call(SM, {destroy_session, ClientId}).
|
|
||||||
|
|
||||||
call(SM, Req) -> gen_server:call(SM, Req).
|
|
||||||
|
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
%%% gen_server callbacks
|
%%% gen_server callbacks
|
||||||
|
@ -126,37 +117,28 @@ init([Id, StatsFun]) ->
|
||||||
gproc_pool:connect_worker(?SM_POOL, {?MODULE, Id}),
|
gproc_pool:connect_worker(?SM_POOL, {?MODULE, Id}),
|
||||||
{ok, #state{id = Id, statsfun = StatsFun}}.
|
{ok, #state{id = Id, statsfun = StatsFun}}.
|
||||||
|
|
||||||
|
%% persistent session
|
||||||
handle_call({start_session, {false, ClientId, ClientPid}}, _From, State) ->
|
handle_call({start_session, {false, ClientId, ClientPid}}, _From, State) ->
|
||||||
Reply =
|
case lookup_session(ClientId) of
|
||||||
case ets:lookup(?SESSION_TAB, ClientId) of
|
undefined ->
|
||||||
[{_Clean, _, SessPid, _MRef}] ->
|
%% create session locally
|
||||||
emqttd_session:resume(SessPid, ClientId, ClientPid),
|
{reply, create_session(false, ClientId, ClientPid), State};
|
||||||
{ok, SessPid};
|
Session ->
|
||||||
[] ->
|
{reply, resume_session(Session, ClientPid), State}
|
||||||
new_session(false, ClientId, ClientPid)
|
end;
|
||||||
end,
|
|
||||||
{reply, Reply, setstats(State)};
|
|
||||||
|
|
||||||
handle_call({start_session, {true, ClientId, ClientPid}}, _From, State) ->
|
handle_call({start_session, {true, ClientId, ClientPid}}, _From, State) ->
|
||||||
case ets:lookup(?SESSION_TAB, ClientId) of
|
case lookup_session(ClientId) of
|
||||||
[{_Clean, _, SessPid, MRef}] ->
|
undefined ->
|
||||||
erlang:demonitor(MRef, [flush]),
|
{reply, create_session(true, ClientId, ClientPid), State};
|
||||||
emqttd_session:destroy(SessPid, ClientId);
|
Session ->
|
||||||
[] ->
|
case destroy_session(Session) of
|
||||||
ok
|
ok ->
|
||||||
end,
|
{reply, create_session(true, ClientId, ClientPid), State};
|
||||||
{reply, new_session(true, ClientId, ClientPid), setstats(State)};
|
{error, Error} ->
|
||||||
|
{reply, {error, Error}, State}
|
||||||
handle_call({destroy_session, ClientId}, _From, State) ->
|
end
|
||||||
case ets:lookup(?SESSION_TAB, ClientId) of
|
end;
|
||||||
[{_Clean, _, SessPid, MRef}] ->
|
|
||||||
emqttd_session:destroy(SessPid, ClientId),
|
|
||||||
erlang:demonitor(MRef, [flush]),
|
|
||||||
ets:delete(?SESSION_TAB, ClientId);
|
|
||||||
[] ->
|
|
||||||
ignore
|
|
||||||
end,
|
|
||||||
{reply, ok, setstats(State)};
|
|
||||||
|
|
||||||
handle_call(_Request, _From, State) ->
|
handle_call(_Request, _From, State) ->
|
||||||
{reply, ok, State}.
|
{reply, ok, State}.
|
||||||
|
@ -164,8 +146,11 @@ handle_call(_Request, _From, State) ->
|
||||||
handle_cast(_Msg, State) ->
|
handle_cast(_Msg, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info({'DOWN', MRef, process, DownPid, _Reason}, State) ->
|
handle_info({'DOWN', _MRef, process, DownPid, _Reason}, State) ->
|
||||||
ets:match_delete(?SESSION_TAB, {'_', '_', DownPid, MRef}),
|
mnesia:transaction(fun() ->
|
||||||
|
[mnesia:delete_object(session, Sess, write) || Sess
|
||||||
|
<- mnesia:index_read(session, DownPid, #mqtt_session.sess_pid)]
|
||||||
|
end),
|
||||||
{noreply, setstats(State)};
|
{noreply, setstats(State)};
|
||||||
|
|
||||||
handle_info(_Info, State) ->
|
handle_info(_Info, State) ->
|
||||||
|
@ -181,17 +166,116 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%%% Internal functions
|
%%% Internal functions
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
|
|
||||||
new_session(CleanSess, ClientId, ClientPid) ->
|
create_session(CleanSess, ClientId, ClientPid) ->
|
||||||
case emqttd_session_sup:start_session(CleanSess, ClientId, ClientPid) of
|
case emqttd_session_sup:start_session(CleanSess, ClientId, ClientPid) of
|
||||||
{ok, SessPid} ->
|
{ok, SessPid} ->
|
||||||
MRef = erlang:monitor(process, SessPid),
|
Session = #mqtt_session{client_id = ClientId,
|
||||||
ets:insert(?SESSION_TAB, {CleanSess, ClientId, SessPid, MRef}),
|
sess_pid = SessPid,
|
||||||
{ok, SessPid};
|
persistent = not CleanSess,
|
||||||
|
on_node = node()},
|
||||||
|
case insert_session(Session) of
|
||||||
|
{aborted, {conflict, Node}} ->
|
||||||
|
%% conflict with othe node?
|
||||||
|
lager:critical("Session ~s conflict with node ~p!", [ClientId, Node]),
|
||||||
|
{error, conflict};
|
||||||
|
{atomic, ok} ->
|
||||||
|
erlang:monitor(process, SessPid),
|
||||||
|
{ok, SessPid}
|
||||||
|
end;
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
{error, Error}
|
{error, Error}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
setstats(State = #state{statsfun = {CFun, SFun}}) ->
|
insert_session(Session = #mqtt_session{client_id = ClientId}) ->
|
||||||
CFun(ets:info(?SESSION_TAB, size)),
|
mnesia:transaction(fun() ->
|
||||||
SFun(ets:select_count(?SESSION_TAB, [{{false, '_', '_', '_'}, [], [true]}])),
|
case mnesia:wread({session, ClientId}) of
|
||||||
|
[] ->
|
||||||
|
mnesia:write(session, Session, write);
|
||||||
|
[#mqtt_session{on_node = Node}] ->
|
||||||
|
mnesia:abort({conflict, Node})
|
||||||
|
end
|
||||||
|
end).
|
||||||
|
|
||||||
|
%% local node
|
||||||
|
resume_session(#mqtt_session{client_id = ClientId,
|
||||||
|
sess_pid = SessPid,
|
||||||
|
on_node = Node}, ClientPid)
|
||||||
|
when Node =:= node() ->
|
||||||
|
case is_process_alive(SessPid) of
|
||||||
|
true ->
|
||||||
|
emqttd_session:resume(SessPid, ClientId, ClientPid),
|
||||||
|
{ok, SessPid};
|
||||||
|
false ->
|
||||||
|
lager:critical("Session ~s@~p died unexpectedly!", [ClientId, SessPid]),
|
||||||
|
{error, session_died}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%% remote node
|
||||||
|
resume_session(Session = #mqtt_session{client_id = ClientId,
|
||||||
|
sess_pid = SessPid,
|
||||||
|
on_node = Node}, ClientPid) ->
|
||||||
|
case emqttd:is_running(Node) of
|
||||||
|
true ->
|
||||||
|
case rpc:call(Node, emqttd_session, resume, [SessPid, ClientId, ClientPid]) of
|
||||||
|
ok ->
|
||||||
|
{ok, SessPid};
|
||||||
|
{badrpc, Reason} ->
|
||||||
|
lager:critical("Resume session ~s on remote node ~p failed for ~p",
|
||||||
|
[ClientId, Node, Reason]),
|
||||||
|
{error, list_to_atom("session_" ++ atom_to_list(Reason))}
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
lager:critical("Session ~s died for node ~p down!", [ClientId, Node]),
|
||||||
|
remove_session(Session),
|
||||||
|
{error, session_node_down}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% local node
|
||||||
|
destroy_session(Session = #mqtt_session{client_id = ClientId,
|
||||||
|
sess_pid = SessPid,
|
||||||
|
on_node = Node}) when Node =:= node() ->
|
||||||
|
case is_process_alive(SessPid) of
|
||||||
|
true ->
|
||||||
|
emqttd_session:destroy(SessPid, ClientId);
|
||||||
|
false ->
|
||||||
|
lager:critical("Session ~s@~p died unexpectedly!", [ClientId, SessPid])
|
||||||
|
end,
|
||||||
|
case remove_session(Session) of
|
||||||
|
{atomic, ok} -> ok;
|
||||||
|
{aborted, Error} -> {error, Error}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%% remote node
|
||||||
|
destroy_session(Session = #mqtt_session{client_id = ClientId,
|
||||||
|
sess_pid = SessPid,
|
||||||
|
on_node = Node}) ->
|
||||||
|
case emqttd:is_running(Node) of
|
||||||
|
true ->
|
||||||
|
case rpc:call(Node, emqttd_session, destroy, [SessPid, ClientId]) of
|
||||||
|
ok ->
|
||||||
|
case remove_session(Session) of
|
||||||
|
{atomic, ok} -> ok;
|
||||||
|
{aborted, Error} -> {error, Error}
|
||||||
|
end;
|
||||||
|
{badrpc, Reason} ->
|
||||||
|
lager:critical("Destroy session ~s on remote node ~p failed for ~p",
|
||||||
|
[ClientId, Node, Reason]),
|
||||||
|
{error, list_to_atom("session_" ++ atom_to_list(Reason))}
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
lager:error("Session ~s died for node ~p down!", [ClientId, Node]),
|
||||||
|
case remove_session(Session) of
|
||||||
|
{atomic, ok} -> ok;
|
||||||
|
{aborted, Error} -> {error, Error}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
remove_session(Session) ->
|
||||||
|
mnesia:transaction(fun() ->
|
||||||
|
mnesia:delete_object(session, Session, write)
|
||||||
|
end).
|
||||||
|
|
||||||
|
setstats(State = #state{statsfun = _StatsFun}) ->
|
||||||
State.
|
State.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -43,20 +43,15 @@ start_link() ->
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
ets:new(emqttd_sm:table(), [set, named_table, public, {keypos, 2},
|
|
||||||
{write_concurrency, true}]),
|
|
||||||
Schedulers = erlang:system_info(schedulers),
|
Schedulers = erlang:system_info(schedulers),
|
||||||
gproc_pool:new(emqttd_sm:pool(), hash, [{size, Schedulers}]),
|
gproc_pool:new(emqttd_sm:pool(), hash, [{size, Schedulers}]),
|
||||||
|
StatsFun = emqttd_stats:statsfun('sessions/count', 'sessions/max'),
|
||||||
Children = lists:map(
|
Children = lists:map(
|
||||||
fun(I) ->
|
fun(I) ->
|
||||||
Name = {emqttd_sm, I},
|
Name = {emqttd_sm, I},
|
||||||
gproc_pool:add_worker(emqttd_sm:pool(), Name, I),
|
gproc_pool:add_worker(emqttd_sm:pool(), Name, I),
|
||||||
{Name, {emqttd_sm, start_link, [I, statsfun()]},
|
{Name, {emqttd_sm, start_link, [I, StatsFun]},
|
||||||
permanent, 10000, worker, [emqttd_sm]}
|
permanent, 10000, worker, [emqttd_sm]}
|
||||||
end, lists:seq(1, Schedulers)),
|
end, lists:seq(1, Schedulers)),
|
||||||
{ok, {{one_for_all, 10, 100}, Children}}.
|
{ok, {{one_for_all, 10, 100}, Children}}.
|
||||||
|
|
||||||
statsfun() ->
|
|
||||||
{emqttd_stats:statsfun('clients/count', 'clients/max'),
|
|
||||||
emqttd_stats:statsfun('sessions/count', 'sessions/max')}.
|
|
||||||
|
|
||||||
|
|
|
@ -28,5 +28,5 @@
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
||||||
%% TODO:... 0.9.0...
|
%% TODO:... 0.10.0...
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_topic).
|
-module(emqttd_topic).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
|
@ -52,7 +52,6 @@
|
||||||
start_link() ->
|
start_link() ->
|
||||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||||
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Start to trace client or topic.
|
%% @doc Start to trace client or topic.
|
||||||
%% @end
|
%% @end
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
|
|
||||||
-module(emqttd_vm).
|
-module(emqttd_vm).
|
||||||
|
|
||||||
|
-export([schedulers/0]).
|
||||||
|
|
||||||
-export([microsecs/0]).
|
-export([microsecs/0]).
|
||||||
|
|
||||||
-export([loads/0, scheduler_usage/1]).
|
-export([loads/0, scheduler_usage/1]).
|
||||||
|
@ -164,6 +166,9 @@
|
||||||
sndbuf,
|
sndbuf,
|
||||||
tos]).
|
tos]).
|
||||||
|
|
||||||
|
schedulers() ->
|
||||||
|
erlang:system_info(schedulers).
|
||||||
|
|
||||||
microsecs() ->
|
microsecs() ->
|
||||||
{Mega, Sec, Micro} = erlang:now(),
|
{Mega, Sec, Micro} = erlang:now(),
|
||||||
(Mega * 1000000 + Sec) * 1000000 + Micro.
|
(Mega * 1000000 + Sec) * 1000000 + Micro.
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
terminate/2, code_change/3]).
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
%% WebSocket Loop State
|
%% WebSocket Loop State
|
||||||
-record(wsocket_state, {request, client_pid, packet_opts, parser_state}).
|
-record(wsocket_state, {request, client_pid, packet_opts, parser}).
|
||||||
|
|
||||||
%% Client State
|
%% Client State
|
||||||
-record(client_state, {ws_pid, request, proto_state, keepalive}).
|
-record(client_state, {ws_pid, request, proto_state, keepalive}).
|
||||||
|
@ -59,7 +59,7 @@ start_link(Req) ->
|
||||||
ReentryWs(#wsocket_state{request = Req,
|
ReentryWs(#wsocket_state{request = Req,
|
||||||
client_pid = ClientPid,
|
client_pid = ClientPid,
|
||||||
packet_opts = PktOpts,
|
packet_opts = PktOpts,
|
||||||
parser_state = emqttd_parser:init(PktOpts)}).
|
parser = emqttd_parser:new(PktOpts)}).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @private
|
%% @private
|
||||||
|
@ -77,14 +77,14 @@ ws_loop(<<>>, State, _ReplyChannel) ->
|
||||||
State;
|
State;
|
||||||
ws_loop([<<>>], State, _ReplyChannel) ->
|
ws_loop([<<>>], State, _ReplyChannel) ->
|
||||||
State;
|
State;
|
||||||
ws_loop(Data, State = #wsocket_state{request = Req,
|
ws_loop(Data, State = #wsocket_state{request = Req,
|
||||||
client_pid = ClientPid,
|
client_pid = ClientPid,
|
||||||
parser_state = ParserState}, ReplyChannel) ->
|
parser = Parser}, ReplyChannel) ->
|
||||||
Peer = Req:get(peer),
|
Peer = Req:get(peer),
|
||||||
lager:debug("RECV from ~s(WebSocket): ~p", [Peer, Data]),
|
lager:debug("RECV from ~s(WebSocket): ~p", [Peer, Data]),
|
||||||
case emqttd_parser:parse(iolist_to_binary(Data), ParserState) of
|
case Parser(iolist_to_binary(Data)) of
|
||||||
{more, ParserState1} ->
|
{more, NewParser} ->
|
||||||
State#wsocket_state{parser_state = ParserState1};
|
State#wsocket_state{parser = NewParser};
|
||||||
{ok, Packet, Rest} ->
|
{ok, Packet, Rest} ->
|
||||||
gen_server:cast(ClientPid, {received, Packet}),
|
gen_server:cast(ClientPid, {received, Packet}),
|
||||||
ws_loop(Rest, reset_parser(State), ReplyChannel);
|
ws_loop(Rest, reset_parser(State), ReplyChannel);
|
||||||
|
@ -93,8 +93,8 @@ ws_loop(Data, State = #wsocket_state{request = Req,
|
||||||
exit({shutdown, Error})
|
exit({shutdown, Error})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
reset_parser(State = #wsocket_state{packet_opts = PktOpts}) ->
|
reset_parser(State = #wsocket_state{packet_opts = PktOpts}) ->
|
||||||
State#wsocket_state{parser_state = emqttd_parser:init(PktOpts)}.
|
State#wsocket_state{parser = emqttd_parser:new (PktOpts)}.
|
||||||
|
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
%%% gen_fsm callbacks
|
%%% gen_fsm callbacks
|
||||||
|
|
Loading…
Reference in New Issue