-module(socket_server). -behaviour(gen_server). -include("../../include/types_network.hrl"). -include_lib("kernel/include/logger.hrl"). -record(state, { socket_server_info :: #socket_info{}, socket_server_pid, clients =[]}). -define(TIMEOUT, 40000). -define(LogFile, "log/socket.log"). -export([send_to_last_accepted/2]). -export([start_link/1, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, stat/1]). -export([internal_receive_from_client/4, internal_accept/5, internal_handle_raw_bytes/5, internal_execute_handle_on_messages/5, internal_handle_one_message/4, internal_is_valid_ip/2]). %% ------------------------------------------------------------------ %% API Function Definitions %% ------------------------------------------------------------------ -spec(start_link(#socket_info{}) -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). start_link(#socket_info{name = Name} = SocketInfo) -> ?LOG_INFO("Start link server socket: ~w~n", [?MODULE], SocketInfo#socket_info.log_file), gen_server:start_link({local, Name}, ?MODULE, [SocketInfo], []). send_to_last_accepted(ServerName, Bytes) -> gen_server:call(ServerName , {send_to_last_accepted, Bytes}). stat(ServerName) -> gen_server:call(ServerName, stat). %% ------------------------------------------------------------------ %% gen_server Function Definitions %% ------------------------------------------------------------------ -spec(init([#socket_info{}]) -> {ok, State :: #state{}}). init([#socket_info{name = Name} = SocketInfo]) -> ?LOG_INFO("Init server socket: ~p~n", [SocketInfo], SocketInfo#socket_info.log_file), gen_server:cast(Name, {init, SocketInfo}), {ok, #state{}}. %% ------------------------------------------------------------------ %% handle_call %% ------------------------------------------------------------------ handle_call({send_to_last_accepted, Bytes}, _From, #state{ clients = Clients, socket_server_info = SocketInfo} = State) -> LogFile = SocketInfo#socket_info.log_file, Res = case Clients of [] -> ?LOG_INFO("Clients is absent message ignored: ~p~n", [Bytes], LogFile), {error, no_clients}; [Head | _] -> ResSend = case gen_tcp:send(Head, Bytes) of ok -> ?LOG_INFO("Send [ok] to ~w ~w bytes: ~s~n", [Head, erlang:byte_size(Bytes), bytes_extension:bin_to_hexstr(Bytes)], LogFile), ok; {error, Reason} -> ?LOG_INFO("Send [error: ~w] to ~w ~w bytes: ~s~n", [Reason, Head, erlang:byte_size(Bytes), bytes_extension:bin_to_hexstr(Bytes)], LogFile), {error, Reason} end, ResSend end, {reply, Res, State}; %% ------------------------------------------------------------------ handle_call(stat, _From, State = #state{clients = Clients, socket_server_info = ServerInfo}) -> {reply, {ok, ServerInfo, Clients}, State}; %% ------------------------------------------------------------------ handle_call(_Request, _From, State) -> {reply, ok, State}. %% ------------------------------------------------------------------ %% handle_cast %% ------------------------------------------------------------------ handle_cast({init, SocketInfo}, _State) -> #socket_info{port = Port, log_file = LogFile, handler = Handler, name = Name, valid_ip = ValidIP} = SocketInfo, socket_utilites:timeout_seconds(1000), ?LOG_INFO("Init socket: ~w, Server: ~w~n", [?MODULE, SocketInfo], LogFile), case gen_tcp:listen(Port, [binary, {packet, 0},{active, false}, {reuseaddr, true}]) of {ok, ServerSocketPid} -> ?LOG_INFO("Started server on port: ~p. ~p~n", [Port, ServerSocketPid], LogFile), Pid = spawn_link(?MODULE, internal_accept, [ServerSocketPid, Handler, Name, ValidIP, LogFile]), ?LOG_INFO("Accept Pid: ~p~n", [Pid], LogFile), {noreply, #state{socket_server_info = SocketInfo, socket_server_pid = ServerSocketPid}}; {error, Reason} -> ?LOG_INFO("Error starting listen: ~p~n", [Reason], LogFile), error(Reason) end; %% ------------------------------------------------------------------ handle_cast({remove_client, ClientPid}, #state{clients = Clients} = State) -> NewClients = [S || S <- Clients, S /= ClientPid], {noreply, State#state{clients = NewClients}}; %% ------------------------------------------------------------------ handle_cast({accept_socket_client, SocketClient}, #state{clients=Clients, socket_server_info = SocketInfo} = State) -> #socket_info{log_file = LogFile} = SocketInfo, NewClients=[SocketClient | Clients], ?LOG_INFO("Active clients [~w]: ~w~n", [length(NewClients), NewClients], LogFile), NewState = State#state{ clients=NewClients }, {noreply, NewState}. %% ------------------------------------------------------------------ %% handle_info %% ------------------------------------------------------------------ handle_info(test, State) -> ?LOG_INFO("State: ~p~n", [State]), {noreply, State}; %% ------------------------------------------------------------------ handle_info(_Info, State) -> ?LOG_INFO("Handle_info: unknown",?LogFile), {noreply, State}. %% ------------------------------------------------------------------ %% terminate %% ------------------------------------------------------------------ terminate(_Reason, _State) -> ?LOG_INFO("Terminate: unknown",?LogFile), ok. %% ------------------------------------------------------------------ %% code_change %% ------------------------------------------------------------------ code_change(_OldVsn, State, _Extra) -> ?LOG_INFO("Code_change: unknown",?LogFile), {ok, State}. %% ------------------------------------------------------------------ %% Пока сокет жив - ожидаем подключения %% ------------------------------------------------------------------ internal_accept(ServerSocketPid, Handler, Name, ValidIP, LogFile) -> ?LOG_INFO("Begin accept: ~p~n", [ServerSocketPid], LogFile), Res = gen_tcp:accept(ServerSocketPid), case Res of {ok, SocketClientPid} -> {ok,{Ip,Port}} = inet:peername(SocketClientPid), case internal_is_valid_ip(Ip, ValidIP) of true -> ?LOG_INFO("Accepted client ~p: ~w:~w~n", [SocketClientPid, Ip,Port], LogFile), gen_server:cast(Name, {accept_socket_client, SocketClientPid}), spawn(?MODULE, internal_receive_from_client, [SocketClientPid, Handler, Name, LogFile]), ok; false -> ?LOG_INFO("Decline accept for client ~p: ~w:~w~n", [SocketClientPid, Ip,Port], LogFile), gen_tcp:close(SocketClientPid), ok end, internal_accept(ServerSocketPid, Handler, Name, ValidIP, LogFile); {error, Reason} -> ?LOG_INFO("Error accept: ~w~n",[{Reason}], LogFile), error({error, Reason}), {stopped, Reason} end. %% ------------------------------------------------------------------ internal_receive_from_client(SocketClientPid, Handler, Name, LogFile) -> ?LOG_INFO("Start read from client ~w~n", [SocketClientPid], LogFile), case gen_tcp:recv(SocketClientPid, 0) of {ok, Bytes} -> ?LOG_INFO("Receive message from ~w ~w bytes: ~s~n", [SocketClientPid, erlang:byte_size(Bytes), bytes_extension:bin_to_hexstr(Bytes)], LogFile), spawn(?MODULE, internal_handle_raw_bytes, [Handler, SocketClientPid, Bytes, Name, LogFile]), internal_receive_from_client(SocketClientPid, Handler, Name, LogFile); {error, closed} -> gen_server:cast(Name, {remove_client, SocketClientPid}), ?LOG_INFO("Client disconnected: ~w~n",[{SocketClientPid}], LogFile), {error, closed}; {error, Reason} -> ?LOG_INFO("Error receive: ~w~n",[{Reason}], LogFile), {error, Reason}; Other -> ?LOG_INFO("Other receive tcp message: ~w~n",[Other], LogFile), {error, Other} end. %% ------------------------------------------------------------------ %% Разбить на сообщения и выполнить %% ------------------------------------------------------------------ internal_handle_raw_bytes(Handler, SocketClientPid, MessageBytes, Name, LogFile) -> Messages = socket_utilites:prepare_l2l1_messages_from_bytes(binary:bin_to_list(MessageBytes), []), internal_execute_handle_on_messages(Handler, SocketClientPid, Messages, Name, LogFile) . %% ------------------------------------------------------------------ %% Запустить на обработку все сообщения %% ------------------------------------------------------------------ internal_execute_handle_on_messages(_Handler, _SocketClientPid, [], _Name, _LogFile) -> ok; internal_execute_handle_on_messages(Handler, SocketClientPid, [{OneMessage} | OtherMessage], Name, LogFile) -> spawn(?MODULE, internal_handle_one_message, [Handler, OneMessage, SocketClientPid, LogFile]), internal_execute_handle_on_messages(Handler, SocketClientPid, OtherMessage, Name, LogFile). %% ------------------------------------------------------------------ %% Запустить на обработку одно конкретное сообщение %% ------------------------------------------------------------------ internal_handle_one_message(Handler, Message, SocketClientPid, LogFile) -> Handler(Message, SocketClientPid, LogFile). internal_is_valid_ip(_, undefined) -> true; internal_is_valid_ip(_, []) -> false; internal_is_valid_ip(OneValidIP, [OneValidIP | _]) -> true; internal_is_valid_ip({N1, _N2, _N3, _N4}, [{N1, -1, -1, -1 } | _]) -> true; internal_is_valid_ip({N1, N2, _N3, _N4}, [{N1, N2, -1, -1 } | _]) -> true; internal_is_valid_ip({N1, N2, N3, _N4}, [{N1, N2, N3, -1 } | _]) -> true; internal_is_valid_ip(IP, [ _ | Tail]) -> internal_is_valid_ip(IP, Tail).