在上一篇基礎上,繼續完善app。新增心跳檢測機制。
修改檔案echo_server.erl如下:
-module(echo_server).
-include("../deps/esockd/include/esockd.hrl").
-behaviour(gen_server).
%% start
-export([start/0, start/1]).
-export([start_link/1]).
%% gen_server Function Exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {conn, connname, peername, peerhost, peerport, keepalive}).
-define(TCP_OPTIONS, [
binary,
{packet, raw},
{buffer, 1024},
{reuseaddr, true},
{backlog, 512},
{nodelay, false}]).
-define(TCP_PORT, 5000).
-define(TIMEOUT_SEC, 30).%unit:s
%% API
start() ->
start(?TCP_PORT).
start([Port]) when is_atom(Port) ->
start(list_to_integer(atom_to_list(Port)));
start(Port) when is_integer(Port) ->
%% application:start(sasl),
%% ok = esockd:start(),
Access = application:get_env(esockd, access, [{allow, all}]),
SockOpts = [{access, Access},
{acceptors, 32},
{max_clients, 1000000},
{sockopts, ?TCP_OPTIONS}],
MFArgs = {?MODULE, start_link, []},
esockd:open(echo, Port, SockOpts, MFArgs).
%% eSockd Callbacks
start_link(Conn) ->
{ok, proc_lib:spawn_link(?MODULE, init, [Conn])}.
init(Conn) ->
{ok, Conn1} = Conn:wait(),
{PeerHost, PeerPort, PeerName} =
case Conn1:peername() of
{ok, Peer = {Host, Port}} ->
{Host, Port, Peer};
{error, enotconn} ->
Conn1:fast_close(),
exit(normal);
{error, Reason} ->
Conn1:fast_close(),
exit({shutdown, Reason})
end,
ConnName = esockd_net:format(PeerName),
%%io:format("~s~n", [esockd_net:format(peername, PeerName)]),
io:format("tcp_connected,~s~n", [esockd_net:format({PeerHost, PeerPort})]),
start_keepalive(?TIMEOUT_SEC),
Conn1:setopts([{active, once}]),
gen_server:enter_loop(?MODULE, [], #state{conn = Conn1,
connname = ConnName,
peername = PeerName,
peerhost = PeerHost,
peerport = PeerPort}).
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({tcp, Sock, Data}, State = #state{conn = ?ESOCK(Sock) = Conn}) ->
{ok, PeerName} = Conn:peername(),
io:format("~s - ~s~n", [esockd_net:format(peername, PeerName), Data]),
Conn:send(Data),
Conn:setopts([{active, once}]),
{noreply, State};
handle_info({tcp_error, Sock, Reason}, State = #state{conn = ?ESOCK(Sock)}) ->
io:format("tcp_error: ~s~n", [Reason]),
{stop, {shutdown, {tcp_error, Reason}}, State};
handle_info({tcp_closed, Sock}, State = #state{conn = ?ESOCK(Sock), peername = PeerName}) ->
io:format("tcp_closed,~s~n", [esockd_net:format(peername, PeerName)]),
{stop, normal, State};
handle_info({keepalive, start, Interval}, State = #state{conn = Conn1}) ->
io:format("Keepalive at the interval of ~p~n", [Interval]),
{ok, KeepAlive} = esockd_keepalive:start(Conn1, Interval, {keepalive, check}),
hibernate(State#state{keepalive = KeepAlive});
handle_info({keepalive, check}, State = #state{peername = PeerName, keepalive = KeepAlive}) ->
io:format("Keepalive check,~s,", [esockd_net:format(peername, PeerName)]),
case esockd_keepalive:resume(KeepAlive) of
{resumed, KeepAlive1} ->
io:format("Keepalive resumed~n"),
hibernate(State#state{keepalive = KeepAlive1});
{error, timeout} ->
io:format("Keepalive timeout~n"),
shutdown(keepalive_timeout, State);
{error, Error} ->
io:format("Keepalive error - ~p~n", [Error]),
shutdown(Error, State)
end;
handle_info(_Info, State) ->
{noreply, State}.
%terminate(_Reason, _State) ->
% ok.
terminate(_Reason, #state{conn = Conn1,
keepalive = KeepAlive}) ->
Conn1:fast_close(),
esockd_keepalive:cancel(KeepAlive),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
start_keepalive(0) -> ignore;
start_keepalive(Sec) when Sec > 0 ->
self() ! {keepalive, start, round(Sec * 1.20)}.
hibernate(State) ->
{noreply, State, hibernate}.
shutdown(Reason, State) ->
stop({shutdown, Reason}, State).
stop(Reason, State) ->
{stop, Reason, State}.
如果想知道目前用戶端的數目,可以在init函數實作:
init(Conn) ->
{ok, Conn1} = Conn:wait(),
{PeerHost, PeerPort, PeerName} =
case Conn1:peername() of
{ok, Peer = {Host, Port}} ->
{Host, Port, Peer};
{error, enotconn} ->
Conn1:fast_close(),
exit(normal);
{error, Reason} ->
Conn1:fast_close(),
exit({shutdown, Reason})
end,
ConnName = esockd_net:format(PeerName),
%%io:format("~s~n", [esockd_net:format(peername, PeerName)]),
io:format("tcp_connected,~s~n", [esockd_net:format({PeerHost, PeerPort})]),
start_keepalive(?TIMEOUT_SEC),
Conn1:setopts([{active, once}]),
%% method 1:
lists:foreach(fun({{Protocol, ListenOn}, Pid}) ->
Info = [{acceptors, esockd:get_acceptors(Pid)},
{max_clients, esockd:get_max_clients(Pid)},
{current_clients,esockd:get_current_clients(Pid)},
{shutdown_count, esockd:get_shutdown_count(Pid)}],
io:format("listener on ~s:~s~n", [Protocol, esockd:to_string(ListenOn)]),
lists:foreach(fun({Key, Val}) ->
io:format(" ~-16s: ~w~n", [Key, Val])
end, Info)
end, esockd:listeners()),
%% method 2:
Pid1 = esockd:listener({echo, ?TCP_PORT}),
Count1 = esockd:get_current_clients(Pid1),
io:format("current_clients:~p~n", [Count1]),
%% method 3:
Count2 = esockd:get_current_clients({echo, ?TCP_PORT}),
io:format("current_clients:~p~n", [Count2]),
gen_server:enter_loop(?MODULE, [], #state{conn = Conn1,
connname = ConnName,
peername = PeerName,
peerhost = PeerHost,
peerport = PeerPort}).
完整的源碼下載下傳位址:
http://download.csdn.net/download/libaineu2004/10113319