KeepAlive

This commit is contained in:
Ery Lee 2015-01-10 16:46:11 +08:00
parent 24d9b46836
commit 52c3bc9628
5 changed files with 140 additions and 118 deletions

View File

@ -40,13 +40,14 @@
%%Client State... %%Client State...
-record(state, { -record(state, {
socket, socket,
peer_name,
conn_name, conn_name,
await_recv, await_recv,
conn_state, conn_state,
conserve, conserve,
parse_state, parse_state,
proto_state, proto_state,
keep_alive keepalive
}). }).
start_link(Sock) -> start_link(Sock) ->
@ -59,25 +60,25 @@ go(Pid, Sock) ->
gen_server:call(Pid, {go, Sock}). gen_server:call(Pid, {go, Sock}).
init([Sock]) -> init([Sock]) ->
io:format("client is created: ~p~n", [self()]), {ok, #state{socket = Sock}, 1000}.
{ok, #state{socket = Sock}, hibernate}.
handle_call({go, Sock}, _From, #state{socket = Sock}) -> handle_call({go, Sock}, _From, #state{socket = Sock}) ->
{ok, Peername} = emqtt_net:peer_string(Sock),
{ok, ConnStr} = emqtt_net:connection_string(Sock, inbound), {ok, ConnStr} = emqtt_net:connection_string(Sock, inbound),
io:format("conn from ~s~n", [ConnStr]), lager:debug("connection: ~s~n", [ConnStr]),
{reply, ok, {reply, ok,
control_throttle( control_throttle(
#state{ socket = Sock, #state{ socket = Sock,
peer_name = Peername,
conn_name = ConnStr, conn_name = ConnStr,
await_recv = false, await_recv = false,
conn_state = running, conn_state = running,
conserve = false, conserve = false,
parse_state = emqtt_packet:initial_state(), parse_state = emqtt_packet:initial_state(),
proto_state = emqtt_protocol:initial_state(Sock)})}; proto_state = emqtt_protocol:initial_state(Sock)}), 10000};
handle_call(info, _From, State = #state{ handle_call(info, _From, State = #state{
conn_name=ConnName, conn_name=ConnName, proto_state = ProtoState}) ->
proto_state = ProtoState}) ->
{reply, [{conn_name, ConnName} | emqtt_protocol:info(ProtoState)], State}; {reply, [{conn_name, ConnName} | emqtt_protocol:info(ProtoState)], State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
@ -95,8 +96,8 @@ handle_info({stop, duplicate_id}, State=#state{conn_name=ConnName}) ->
stop({shutdown, duplicate_id}, State); stop({shutdown, duplicate_id}, State);
%%TODO: ok?? %%TODO: ok??
handle_info({dispatch, Msg}, #state{proto_state = ProtoState} = State) -> handle_info({dispatch, Message}, #state{proto_state = ProtoState} = State) ->
{ok, ProtoState1} = emqtt_protocol:send_message(Msg, ProtoState), {ok, ProtoState1} = emqtt_protocol:send_message(Message, ProtoState),
{noreply, State#state{proto_state = ProtoState1}}; {noreply, State#state{proto_state = ProtoState1}};
handle_info({inet_reply, _Ref, ok}, State) -> handle_info({inet_reply, _Ref, ok}, State) ->
@ -109,18 +110,23 @@ handle_info({inet_async, Sock, _Ref, {ok, Data}}, #state{ socket = Sock}=State)
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);
%%TODO: HOW TO HANDLE THIS??
handle_info({inet_reply, _Sock, {error, Reason}}, State) -> handle_info({inet_reply, _Sock, {error, Reason}}, State) ->
lager:critical("unexpected inet_reply '~p'", [Reason]),
{noreply, State}; {noreply, State};
handle_info(keep_alive_timeout, #state{keep_alive=KeepAlive}=State) -> handle_info({keepalive, start, TimeoutSec}, State = #state{socket = Socket}) ->
case emqtt_keep_alive:state(KeepAlive) of lager:info("~s keepalive started: ~p", [State#state.peer_name, TimeoutSec]),
idle -> KeepAlive = emqtt_keepalive:new(Socket, TimeoutSec, {keepalive, timeout}),
lager:info("keep_alive timeout: ~p", [State#state.conn_name]), {noreply, State#state{ keepalive = KeepAlive }};
handle_info({keepalive, timeout}, State = #state { keepalive = KeepAlive }) ->
case emqtt_keepalive:resume(KeepAlive) of
timeout ->
lager:info("~s keepalive timeout!", [State#state.peer_name]),
{stop, normal, State}; {stop, normal, State};
active -> {resumed, KeepAlive1} ->
KeepAlive1 = emqtt_keep_alive:reset(KeepAlive), lager:info("~s keepalive resumed.", [State#state.peer_name]),
{noreply, State#state{keep_alive=KeepAlive1}} {noreply, State#state{ keepalive = KeepAlive1 }}
end; end;
handle_info(Info, State) -> handle_info(Info, State) ->
@ -134,9 +140,9 @@ terminate(Reason, #state{proto_state = unefined}) ->
%emqtt_protocol:client_terminated(ProtoState), %emqtt_protocol:client_terminated(ProtoState),
ok; ok;
terminate(_Reason, #state{proto_state = ProtoState}) -> terminate(_Reason, #state { keepalive = KeepAlive, proto_state = ProtoState }) ->
%%TODO: fix keep_alive... %%TODO: fix keep_alive...
%%emqtt_keep_alive:cancel(KeepAlive), emqtt_keepalive:cancel(KeepAlive),
emqtt_protocol:client_terminated(ProtoState), emqtt_protocol:client_terminated(ProtoState),
ok. ok.

View File

@ -24,43 +24,48 @@
-author('feng@emqtt.io'). -author('feng@emqtt.io').
-export([new/2, -export([new/3, resume/1, cancel/1]).
state/1,
activate/1,
reset/1,
cancel/1]).
-record(keep_alive, {state, period, timer, msg}). -record(keepalive, {socket, recv_oct, timeout_sec, timeout_msg, timer_ref}).
new(undefined, _) -> %%
undefined; %% @doc create a keepalive.
new(0, _) -> %%
undefined; new(Socket, TimeoutSec, TimeoutMsg) when TimeoutSec > 0 ->
new(Period, TimeoutMsg) when is_integer(Period) -> {ok, [{recv_oct, RecvOct}]} = inet:getstate(Socket, [recv_oct]),
Ref = erlang:send_after(Period, self(), TimeoutMsg), Ref = erlang:send_after(TimeoutSec*1000, self(), TimeoutMsg),
#keep_alive{state=idle, period=Period, timer=Ref, msg=TimeoutMsg}. #keepalive { socket = Socket,
recv_oct = RecvOct,
timeout_sec = TimeoutSec,
timeout_msg = TimeoutMsg,
timer_ref = Ref }.
state(undefined) -> %%
undefined; %% @doc try to resume keepalive, called when timeout.
state(#keep_alive{state=State}) -> %%
State. resume(KeepAlive = #keepalive { socket = Socket,
recv_oct = RecvOct,
activate(undefined) -> timeout_sec = TimeoutSec,
undefined; timeout_msg = TimeoutMsg,
activate(KeepAlive) when is_record(KeepAlive, keep_alive) -> timer_ref = Ref }) ->
KeepAlive#keep_alive{state=active}. {ok, [{recv_oct, NewRecvOct}]} = inet:getstate(Socket, [recv_oct]),
if
reset(undefined) -> NewRecvOct =:= RecvOct ->
undefined; timeout;
reset(KeepAlive=#keep_alive{period=Period, timer=Timer, msg=Msg}) -> true ->
catch erlang:cancel_timer(Timer), %need?
Ref = erlang:send_after(Period, self(), Msg), cancel(Ref),
KeepAlive#keep_alive{state=idle, timer = Ref}. NewRef = erlang:send_after(TimeoutSec*1000, self(), TimeoutMsg),
{resumed, KeepAlive#keepalive { recv_oct = NewRecvOct, timer_ref = NewRef }}
end.
%%
%% @doc cancel keepalive
%%
cancel(#keepalive { timer_ref = Ref }) ->
cancel(Ref);
cancel(undefined) -> cancel(undefined) ->
undefined; undefined;
cancel(KeepAlive=#keep_alive{timer=Timer}) -> cancel(Ref) ->
catch erlang:cancel_timer(Timer), catch erlang:cancel_timer(Ref).
KeepAlive#keep_alive{timer=undefined}.

View File

@ -26,7 +26,7 @@
-export([tcp_name/3, tcp_host/1, getopts/2, setopts/2, getaddr/2, port_to_listeners/1]). -export([tcp_name/3, tcp_host/1, getopts/2, setopts/2, getaddr/2, port_to_listeners/1]).
-export([connection_string/2]). -export([peername/1, sockname/1, peer_string/1, connection_string/2]).
-include_lib("kernel/include/inet.hrl"). -include_lib("kernel/include/inet.hrl").
@ -196,6 +196,14 @@ setopts(Sock, Options) when is_port(Sock) ->
sockname(Sock) when is_port(Sock) -> inet:sockname(Sock). sockname(Sock) when is_port(Sock) -> inet:sockname(Sock).
peer_string(Sock) ->
case peername(Sock) of
{ok, {Addr, Port}} ->
{ok, lists:flatten(io_lib:format("~s:~p", [maybe_ntoab(Addr), Port]))};
Error ->
Error
end.
peername(Sock) when is_port(Sock) -> inet:peername(Sock). peername(Sock) when is_port(Sock) -> inet:peername(Sock).
ntoa({0,0,0,0,0,16#ffff,AB,CD}) -> ntoa({0,0,0,0,0,16#ffff,AB,CD}) ->

View File

@ -217,7 +217,7 @@ serialise_variable(#mqtt_packet_header { type = PubAck },
<<>> = _Payload) <<>> = _Payload)
when PubAck =:= ?PUBACK; PubAck =:= ?PUBREC; when PubAck =:= ?PUBACK; PubAck =:= ?PUBREC;
PubAck =:= ?PUBREL; PubAck =:= ?PUBCOMP -> PubAck =:= ?PUBREL; PubAck =:= ?PUBCOMP ->
{PacketIdBin = <<PacketId:16/big>>, <<>>}; {<<PacketId:16/big>>, <<>>};
serialise_variable(#mqtt_packet_header { }, serialise_variable(#mqtt_packet_header { },
undefined, undefined,

View File

@ -103,7 +103,7 @@ handle_packet(?CONNECT, #mqtt_packet {
password = Password, password = Password,
proto_ver = ProtoVersion, proto_ver = ProtoVersion,
clean_sess = CleanSess, clean_sess = CleanSess,
keep_alive = AlivePeriod, keep_alive = KeepAlive,
client_id = ClientId } = Var }, client_id = ClientId } = Var },
State0 = #proto_state{socket = Sock}) -> State0 = #proto_state{socket = Sock}) ->
@ -118,19 +118,18 @@ handle_packet(?CONNECT, #mqtt_packet {
_ -> _ ->
case emqtt_auth:check(Username, Password) of case emqtt_auth:check(Username, Password) of
false -> false ->
lager:error_MSG("MQTT login failed - no credentials"), lager:error("MQTT login failed - no credentials"),
{?CONNACK_CREDENTIALS, State}; {?CONNACK_CREDENTIALS, State};
true -> true ->
lager:info("connect from clientid: ~p, ~p", [ClientId, AlivePeriod]), lager:info("connect from clientid: ~p, keepalive: ", [ClientId, KeepAlive]),
%%TODO: start_keepalive(KeepAlive),
%%KeepAlive = emqtt_keep_alive:new(AlivePeriod*1500, keep_alive_timeout),
emqtt_cm:register(ClientId, self()), emqtt_cm:register(ClientId, self()),
{?CONNACK_ACCEPT, {?CONNACK_ACCEPT,
State #proto_state{ will_msg = make_will_msg(Var), State #proto_state{ will_msg = make_will_msg(Var),
client_id = ClientId }} client_id = ClientId }}
end end
end, end,
lager:info("recv conn...:~p", [ReturnCode]), lager:info("[SENT] MQTT CONNACK: ~p", [ReturnCode]),
send_packet(Sock, #mqtt_packet { send_packet(Sock, #mqtt_packet {
header = #mqtt_packet_header { type = ?CONNACK }, header = #mqtt_packet_header { type = ?CONNACK },
variable = #mqtt_packet_connack{ return_code = ReturnCode }}), variable = #mqtt_packet_connack{ return_code = ReturnCode }}),
@ -358,3 +357,7 @@ send_will_msg(#proto_state{will_msg = undefined}) ->
send_will_msg(#proto_state{will_msg = WillMsg }) -> send_will_msg(#proto_state{will_msg = WillMsg }) ->
emqtt_router:route(WillMsg). emqtt_router:route(WillMsg).
start_keepalive(0) -> ignore;
start_keepalive(Sec) when Sec > 0 ->
self() ! {keepalive, start, Sec * 1.5}.