%%%------------------------------------------------------------------- %%% @author cheese %%% @copyright (C) 2016, %%% @doc %%% %%% @end %%% Created : 19. Feb 2016 15:24 %%%------------------------------------------------------------------- -module(socket_client). -author("cheese"). -behaviour(gen_server). -include("../../include/types_network.hrl"). -include_lib("kernel/include/logger.hrl"). -define(TIMEOUT_CONNECT, 5000). -define(TIMEOUT_START, 3000). -define(REGISTER_CHECK, 7000). -record(state, { socket_info :: #socket_info{}, socket_instance }). %% API -export([start_link/1, send_message/2, handle_message/2, handle_one_message/2, execute_handle_messages/2]). %% gen_server -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% API -spec(start_link(#socket_info{}) -> 'ignore' | {'error',_} | {'ok',pid()}). start_link(Socket) -> Name = Socket#socket_info.name, LogFileName = Socket#socket_info.log_file, ?LOG_INFO("start_link: ~w~n", [?MODULE], LogFileName), ?LOG_INFO("ConsumerSocket ~w: ~p~n", [Name, Socket], LogFileName), consumer_control:unregister(#consumer_info{name = Name}), gen_server:start_link({local, Name}, ?MODULE, [Socket], []). send_message(Bytes, ServerName) -> gen_server:call(ServerName, {send, Bytes}). %%%=================================================================== -spec(init([#socket_info{}]) -> {ok,#state{}}). init([Socket]) -> Name = Socket#socket_info.name, LogFileName = get_file_name(Name), ?LOG_INFO("init: ~w, ~p~n", [?MODULE, Socket], LogFileName), gen_server:cast(Name, {init, Socket}), {ok, #state{}}. %%%=================================================================== handle_call({send, Bytes}, _From, State) -> Res = case gen_tcp:send(State#state.socket_instance, Bytes) of ok -> ?LOG_INFO("Send [ok] to ~w ~w bytes: ~s~n", [State#state.socket_info#socket_info.name, erlang:byte_size(Bytes), bytes_extension:bin_to_hexstr(Bytes)], State#state.socket_info#socket_info.log_file), ok; {error, Reason} -> ?LOG_INFO("Send [error: ~w] to ~w ~w bytes: ~s~n", [Reason, State#state.socket_info#socket_info.name, erlang:byte_size(Bytes), bytes_extension:bin_to_hexstr(Bytes)], State#state.socket_info#socket_info.log_file), {error, Reason} end, {reply, Res, State}; handle_call(_Request, _From, State) -> {reply, ok, State}. %%=================================================================== handle_cast({init, SocketInfo}, State) -> Name = SocketInfo#socket_info.name, LogFileName = SocketInfo#socket_info.log_file, Ip = SocketInfo#socket_info.ip, Port = SocketInfo#socket_info.port, ?LOG_INFO("Try connect to ~p:~w~n", [Ip, Port], LogFileName), Socket = case gen_tcp:connect(Ip, Port, [binary, {packet, 0}, {keepalive, true}], ?TIMEOUT_CONNECT) of {ok, SocketConnected} -> erlang:send_after(?REGISTER_CHECK, self(), {register, Name}), consumer_control:register(#consumer_info{name = Name, pid = self()}), SocketConnected; {error, Reason} -> ?LOG_INFO("Connect failed: ~w~n", [Reason], LogFileName), socket_utilites:timeout_seconds(1000), error(Reason) end, ?LOG_INFO("Connected to ~p:~w success: ~w ~n", [Ip, Port, Socket], LogFileName), NewState = State#state{ socket_info = SocketInfo, socket_instance = Socket }, {noreply, NewState}; %%=================================================================== handle_cast(_Request, State) -> {noreply, State}. %%%=================================================================== handle_info({tcp, RemoteSocket, Bytes}, State) -> Name = State#state.socket_info#socket_info.name, LogFileName = State#state.socket_info#socket_info.log_file, {ok,{Ip,Port}} = inet:peername(RemoteSocket), ?LOG_INFO("Receive message from ~w [~p:~w] ~w bytes: ~s~n", [Name, Ip,Port, erlang:byte_size(Bytes), bytes_extension:bin_to_hexstr(Bytes)], LogFileName), handle_message(Bytes, State), {noreply, State}; %%%=================================================================== handle_info({tcp_closed, RemoteSocket}, State) -> Name = State#state.socket_info#socket_info.name, LogFileName = State#state.socket_info#socket_info.log_file, ?LOG_INFO("Client disconnected: ~w~n", [RemoteSocket], LogFileName), consumer_control:unregister(#consumer_info{name = Name, pid = self()}), error(disconnect), {noreply, State}; %%%=================================================================== handle_info({register, Name}, State) -> consumer_control:register(#consumer_info{name = Name, pid = self()}), {noreply, State}; %%%=================================================================== handle_info(Info, State) -> LogFileName = State#state.socket_info#socket_info.log_file, ?LOG_INFO("Unknown message in socket: ~w~n", [Info], LogFileName), {noreply, State}. %%%=================================================================== terminate(_Reason, _State) -> ok. %%%=================================================================== code_change(_OldVsn, State, _Extra) -> {ok, State}. get_file_name(Name) -> "log/" ++ atom_to_list(Name) ++ ".log". %%*************** %% ------------------------------------------------------------------ %% Если обработчик события есть - обрабатываем %% ------------------------------------------------------------------ handle_message(Bytes, #state{ socket_info = #socket_info{log_file = FileName} } = State ) -> Messages = socket_utilites:prepare_l2l1_messages_from_bytes(binary:bin_to_list(Bytes), []), execute_handle_messages(Messages, State). %% ------------------------------------------------------------------ %% Запустить на обработку все сообщения %% ------------------------------------------------------------------ execute_handle_messages(ignored, #state{ socket_info = #socket_info{log_file = FileName} } ) -> ?LOG_INFO("message ignored", FileName), ok; execute_handle_messages([], _State) -> ok; execute_handle_messages([{HeadMessage} | OtherMessage], State) -> spawn(?MODULE, handle_one_message, [HeadMessage, State]), execute_handle_messages(OtherMessage, State). %% ------------------------------------------------------------------ %% Запустить на обработку одно конкретное сообщение %% ------------------------------------------------------------------ handle_one_message(Bytes, #state{ socket_info = #socket_info{ handler = Handler, log_file = FileName}}) -> case Handler of undefined -> {undefined, handler_is_absent}; _ -> try Handler(Bytes) of Res -> Res catch ErrRes -> ?LOG_INFO("Consumer handler error: ~w~n", [ErrRes], FileName), ErrRes end end.