Current section
Files
Jump to
Current section
Files
src/tcp_server.erl
-module(tcp_server).
-include_lib("kernel/include/logger.hrl").
-behavior(gen_server).
-define(ACCEPT_NUM_DEFAULT, 128).
-define(TCP_OPTIONS_DEFAULT, [
binary, % 传输的是二进制
{packet, raw}, % 不封包
{active, true},
{backlog, 2048},
{reuseaddr, true},
{send_timeout, 5000}
]).
-ifdef(OTP_RELEASE). %% this implies 21 or higher
-define(EXCEPTION(Class, Reason, Stacktrace), Class:Reason:Stacktrace).
-define(GET_STACK(Stacktrace), Stacktrace).
-else.
-define(EXCEPTION(Class, Reason, _), Class:Reason).
-define(GET_STACK(_), erlang:get_stacktrace()).
-endif.
-record(state, {
name,
port, % listen port
accept_num,
tcp_options,
listen_sock, % listen socket
conn = 0, % current connection
conn_max = 50000,% max connection
callback,
sock_map = #{} % ref -> {pid, socket}
}).
-record(callback, {
loop, % 主循环体,进行服务的函数
reg, % 注册函数,在gen_server启动的时候执行,{Module, Func} or fun/0
close,% 服务器退出回调,{Module, Func} or fun/0
reject % 拒绝函数,超过最大连接数执行 {Module, Func} or fun/1 (M:F(Name,Socket))
}).
-export([
init/1,
code_change/3,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2
]).
-export([
start_link/4,
start_link/5,
start_link/6
]).
start_link(Name, Port, Max, Callback) ->
start_link(Name, Port, Max, ?ACCEPT_NUM_DEFAULT, Callback, ?TCP_OPTIONS_DEFAULT).
start_link(Name, Port, Max, AcceptNum, Callback) ->
start_link(Name, Port, Max, AcceptNum, Callback, ?TCP_OPTIONS_DEFAULT).
start_link(Name, Port, Max, AcceptNum, Callback, TcpOptions) ->
State = #state{
name = Name,
port = Port,
conn = 0,
conn_max = Max,
accept_num = AcceptNum,
tcp_options = TcpOptions,
callback = func2callback(Callback),
sock_map = #{}
},
gen_server:start_link({local, Name}, ?MODULE, State, []).
func2callback({_, _} = LoopFunc) ->
func2callback([LoopFunc, undefined, undefined, undefined]);
func2callback([LoopFunc]) ->
func2callback([LoopFunc, undefined, undefined, undefined]);
func2callback([LoopFunc, CloseFunc]) ->
func2callback([LoopFunc, CloseFunc, undefined, undefined]);
func2callback([LoopFunc, CloseFunc, RegFunc]) ->
func2callback([LoopFunc, CloseFunc, RegFunc, undefined]);
func2callback([LoopFunc, CloseFunc, RegFunc, RejectFunc]) ->
#callback{loop = LoopFunc, close = CloseFunc, reg = RegFunc, reject = RejectFunc}.
init(State = #state{name = Name, port = Port, accept_num = AcceptNum, callback = Callback, tcp_options = TcpOptions}) ->
process_flag(trap_exit, true),
?LOG_INFO("~p initializing port ~p", [Name, Port]),
reg_func(Callback),
case gen_tcp:listen(Port, TcpOptions) of
{ok, ListenSock} ->
?LOG_INFO("~p,socket listen success at port ~p", [Name, Port]),
State2 = State#state{listen_sock = ListenSock},
start_accepters(AcceptNum, State2),
{ok, State2};
{error, Why} ->
?LOG_ERROR("~p,socket listen fail at port ~p with ~p", [Port, Why]),
{stop, Why}
end.
handle_call({conn_accept, Pid, Socket}, _, #state{conn = Conn, conn_max = MaxConn, sock_map = M} = State) ->
case Conn < MaxConn of
true ->
Ref = erlang:monitor(process, Pid),
M2 = M#{Ref => {Pid, Socket}},
{reply, true, State#state{conn = Conn + 1, sock_map = M2}};
false ->
{reply, false, State}
end;
handle_call(_Msg, _Caller, State) ->
{noreply, State}.
handle_cast(accept_new, State) ->
proc_lib:spawn(fun() -> accepter(State) end),
{noreply, State};
handle_cast(_Request, State) ->
{noreply, State}.
handle_info({'DOWN', Ref, _Type, _Object, _Info}, #state{sock_map = M, name = Name, conn = Con} = State) ->
erlang:demonitor(Ref, [flush]),
case M of
#{Ref := {P, Socket}} ->
?LOG_INFO("~p, pid ~p, socket ~ts disconnect.", [Name, P, Socket]),
M2 = maps:remove(Ref, M),
{noreply, State#state{sock_map = M2, conn = Con - 1}};
#{} ->
{noreply, State}
end;
handle_info(_Msg, State) ->
{noreply, State}.
code_change(_OldVersion, Library, _Extra) ->
{ok, Library}.
terminate(_Reason, #state{name = Name, callback = Callback}) ->
?LOG_INFO("~p stopping ~n", [Name]),
close_func(Callback),
ok.
% 注册函数执行
reg_func(#callback{reg = {M, F}}) -> M:F();
reg_func(_) -> ok.
loop_func(#callback{loop = {M, F}}, Socket) ->
try
M:F(Socket)
catch
?EXCEPTION(Class, Reason, Stacktrace) ->
gen_tcp:close(Socket),
?LOG_ERROR("tcp loop fail, stacktrace: ~p, class: ~p, reason: ~p ~n", [?GET_STACK(Stacktrace), Class, Reason])
end;
loop_func(_, Socket) ->
gen_tcp:close(Socket).
reject_func(#callback{reject = {M, F}}, Socket) ->
M:F(Socket),
gen_tcp:close(Socket);
reject_func(_, Socket) ->
gen_tcp:close(Socket).
close_func(#callback{close = {M, F}}) -> M:F();
close_func(_) -> ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
start_accepters(Num, _State) when Num < 1 ->
ok;
start_accepters(Num, State) ->
proc_lib:spawn(fun() -> accepter(State) end),
start_accepters(Num - 1, State).
accepter(#state{name = Name, listen_sock = ListenSock, callback = Callback}) ->
case gen_tcp:accept(ListenSock) of
{ok, Socket} ->
Self = self(),
SockStr = socket_ip_str(Socket),
?LOG_INFO("pid:~p,accept socket ~ts", [Self, SockStr]),
gen_server:cast(Name, accept_new),
case gen_server:call(Name, {conn_accept, Self, SockStr}) of
true ->
loop_func(Callback, Socket);
false ->
reject_func(Callback, Socket)
end;
Err ->
?LOG_ERROR("accept socket fail with ~p", [Err]),
gen_server:cast(Name, accept_new),
ok
end.
socket_ip_str(Socket) ->
case inet:peername(Socket) of
{ok, {IP, Port}} ->
case inet:ntoa(IP) of
{error, einval} -> undefined;
Addr -> lists:flatten(io_lib:format("(~ts:~p)", [Addr, Port]))
end;
{error, _} -> undefined
end.