gen_server:call - how to send message - erlang

Now Im playing with the gen_server
I have two modules - one is Gen Server mod, second - logic module
and would like to send the message to the PID through the gen_server:call
here is the snip of code:
lookup_by_date(FromTime, ToTime) ->
gen_server:call({global, ?MODULE}, {lookup_by_date,FromTime,ToTime}).
here is the handle_call func:
handle_call({lookup_by_date, FromTime, ToTime}, _From, _State) ->
FromSec = calendar:datetime_to_gregorian_seconds(FromTime),
ToSec = calendar:datetime_to_gregorian_seconds(ToTime),
Pid = spawn(fun()-> logic:handler() end),
{reply, Pid !{lookup_by_date, FromSec, ToSec}, _State};
aand the logic mod code:
lookup_by_date(FromTime, ToTime) -> lookup_by_date(FromTime, ToTime, ets:first(auth), []).
lookup_by_date(_FromTime, _ToTime, '$end_of_table', Acc) -> {reply, Acc, ok};
lookup_by_date(FromTime, ToTime, Key, Acc) ->
case ets:lookup(auth, Key) of
[{Login, Pass, TTL, Unix, Unix2}] ->
F = calendar:datetime_to_gregorian_seconds(Unix2),
T = calendar:datetime_to_gregorian_seconds(Unix2),
if
F >= FromTime, T =< ToTime -> NewAcc = [{Login, Pass, TTL, Unix, Unix2}|Acc],
N = ets:next(auth, Key),
lookup_by_date(FromTime, ToTime, N, NewAcc);
true -> N = ets:next(auth, Key),
lookup_by_date(FromTime, ToTime, N, Acc)
end
end.
handler() ->
receive
{lookup_by_date, FromTime, ToTime}->
lookup_by_date(FromTime, ToTime),
handler();
Other->
io:format("Error message for ~p~n" ,[Other]),
handler()
end.
but i am getting the error (actually not an error)
2> c(cache_server).
{ok,cache_server}
3> c(logic).
{ok,logic}
4> cache_server:start([{ttl, 15000}]).
{ok,<0.73.0>}
5> cache_server:insert(test, root, 15000).
{auth,test,root,15000,1484309726435,
{{2017,1,13},{14,15,11}}}
6> cache_server:lookup_by_date({{2017,1,13},{14,15,11}},{{2017,1,13},{14,15,11}}).
{lookup_by_date,63651536111,63651536111}
I am receiving data from - {reply, Pid !{lookup_by_date, FromSec, ToSec}, _State};
but dont receive data from the "logic:lookup_by_date" function
Is there anyway you show me the right direction because Im stuck a little bit.
Thx...

In your code, the reply to the gen_server call is:
Pid !{lookup_by_date, FromSec, ToSec}
In Erlang messages are asynchronous, they are just sent to the process, so this code doesn't wait for a response, and it simply returns, immediatly, the message you are sending. It is why you get the reply {lookup_by_date, FromSec, ToSec}.
In your case you don't have to spawn a process, but simply call the lookup_by_date function:
handle_call({lookup_by_date, FromTime, ToTime}, _From, _State) ->
FromSec = calendar:datetime_to_gregorian_seconds(FromTime),
ToSec = calendar:datetime_to_gregorian_seconds(ToTime),
{reply, logic:lookup_by_date(FromSec, ToSec), _State};
Note: Your gen_server doesn't use the result, its state is not modified by the request, so you could directly call the function lookup_by_date and include the time conversion in it.

Related

gen_server:call with new State

A module calls a gen_server to handle a stream, which uses record as State.
handle_call handles stream using a function from State, which separates completed piece of data and tail,
Now next time, the tail should be fed first but with an updated State, before the module sends in more data.
handle_call({stream, Data}, _From, State = #mystate{myfun=Fun}) ->
case Fun(Data) of
{completed piece,tail} ->
dosomethingwithpieace,
NewState = State##mystate{myfun=resetfun()};
% How do i call this again to feed Tail first with new state?
{stillstreaming, Currentstate} ->
NewState = State##mystate{myfun=CurrentState};
I cannot call gen_server:call(self(),{stream, Tail}) because State needs to be updated first.
And I cannot reply with a new State, because module will send in more data and tail will disappear.
Is there a way to call it again with updated State without replying with tail and feeding tail back from module??
Update,Code:
% caller module
case gen_tcp:recv(Socket, 0) of % cannot set Length as it will block untill it is done reading Length number of bytes
{ok, Data} ->
Response = gen_server:call(Pid, {handle_init,Socket,Data}),
case Response of
{ok, continue} ->
pre_loop(Socket, Pid);
{ok, logged_in} ->
{UserId, UserName} = get_user_data(), % ignore it for now.
receiver_loop(UserId, UserName, Socket, Pid);
{stop, Reason} ->
io:format("Error at pre_loop: ~p~n", [Reason]);
_ ->
io:format("Unknown response from call in pre-loop: ~p~n", [Response])
end;
{error, closed} -> % done here as no data was stored in mnesia yet.
gen_server:stop(Pid),
io:format("Client died in pre_loop~n")
end.
and gen_server module:
% gen_server module
handle_call({handle_init, _Socket, Data}, _From, State = #server_state{data_fun = {incomplete, Fun}}) ->
case catch Fun(Data) of
{incomplete, F} ->
NewState = State#server_state{data_fun = {incomplete, F}},
{reply, {ok, continue}, NewState};
{with_tail, Term, Tail} ->
% handle Term login/register only
case handle_input_init(Term, Tail) of
{incomplete, Fn, logged_in} ->
NewState = State#server_state{data_fun = {incomplete, Fn}},
{reply, {ok, logged_in}, NewState};
{incomplete, Fn} ->
NewState = State#server_state{data_fun = {incomplete, Fn}},
{reply, {ok, continue}, NewState};
{stop, malformed_data} ->
{reply, {stop, malformed_data}, State}
end;
_ ->
{reply, {stop, malformed_data}, State}
end;
handle_call(_Message, _From, State = #server_state{}) ->
{reply, {stop , unknown_call}, State}.
handle_input_init(Term, Tail) ->
case handle_term_init(Term) of
{ok, login_passed} ->
io:format("send user a login pass msg"),
handle_tail_init(Tail, logged_in);
{error, login_failed} ->
io:format("send user a login failed error~n"),
handle_tail_init(Tail);
{ok, registration_passed} ->
io:format("send user a registeration passed msg"),
handle_tail_init(Tail);
{error, registration_failed} ->
io:format("send user a registeration failed error"),
handle_tail_init(Tail);
{error, invalidreq} ->
io:format("send user an invalid requst error~n"),
handle_tail_init(Tail)
end.
handle_tail_init(Tail) ->
case catch jsx:decode(Tail, [stream, return_tail, return_maps]) of
{incomplete, F} ->
{incomplete, F};
{with_tail, Term, Tail2} ->
handle_input_init(Term, Tail2);
_ ->
{stop, malformed_data}
end.
handle_tail_init(Tail, logged_in) -> % because it was logged in already, any further requests should be ignored
case catch jsx:decode(Tail, [stream, return_tail, return_maps]) of
{incomplete, F} ->
{incomplete, F, logged_in};
{with_tail, _Term, Tail2} ->
io:format("send user an invalid requst error~n"),
handle_tail_init(Tail2, logged_in);
_ ->
{stop, malformed_data}
end.
handle_term_init(Term) ->
case Term of
#{<<"Login">> := [UserName,Password]} ->
login_user(UserName,Password);
#{<<"Register">> := [UserName,Password]} ->
register_user(UserName,Password);
_ ->
{error, invalidreq}
end.
It is working as expected but this is my very first Erlang code ever and i am positive that it can be simplified to a single recursive handle_call, maintaining OTP style, the reason I chose Erlang.
I cannot call gen_server:call(self(),{stream, Tail}) because State
needs to be updated first.
I can't really understand what you are trying to say, but if you mean:
I cannot recursively call gen_server:call(self(),{stream, Tail}), i.e. I cannot write code in handle:call() that recursively calls handle_call().
then you can certainly send all the data inside handle:call() to another function that recursively calls itself:
handle_call(...) ->
...
NewState = ....
Result = ...
{FinalResult, FinalState} = helper_func(Result, NewState, Tail)
{reply, FinalResult, FinalState}
helper_func(Result, State, []) ->
{Result, State};
helper_func(Result, State, [Piece|Tail]) ->
...
NewState = ...
NewResult = ...
helper_func(NewResult, NewState, Tail). %% Recursive call here

gen_server terminates with reason: bad return value

I was following this example of executing periodic tasks given by Hynek -Pichi- Vychodil here.
And i ran into a problem as I wanted to pass some Arguments also to the start_link function which would be used inside my do_task() function. But as given here the start_link/4 needs to return {ok,Pid} and in my case it is returning {ok,{Ref,Arguments}} and thus is failing.
How I can I fix this up.Here's my code:
start_link(Period,SERVER,Args) when Period > 0, is_integer(Period) ->
gen_server:start_link({local, SERVER}, ?MODULE, [Period,Args], []).
init([Period,Args]) ->
StartT = erlang:monotonic_time(millisecond),
self() ! tick,
{ok, {StartT, Period,Args}}.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(tick, {StartT, Period,Args} = S) ->
Next = Period - (erlang:monotonic_time(millisecond)-StartT) rem Period,
_Timer = erlang:send_after(Next, self(), tick),
do_task(Args),
{ok, S};
handle_info(_Info, State) ->
{noreply, State}.
Here
Period is->30000
and Arguments is -> {A,[a,b],'something'}
And here is the crash log
[error] gen_server '95ef60ae-b2fa-491a-821d-ffae85cc57f6' terminated with reason: bad return value: {ok,{-576460723187,30000,{A,[a,b],'something'}}
handle_info cannot return an ok tuple.
(Answer given as a comment.)

Ejabberd get room online occupants within module

I am currently trying to get the occupants of a room in a custom Ejabberd module within the muc_filter_message hook, but all attempts to get the room state are timing out.
error:
2016-07-25 10:43:04.802 [error] <0.13909.0>#ejabberd_hooks:run_fold1:368 {timeout,{gen_fsm,sync_send_all_state_event,[<0.13909.0>,get_state]}}
imports + hook function (cut down)
-behaviour(gen_mod).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("mod_muc_room.hrl").
-include("mod_muc.hrl").
-include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl").
-include("ejabberd_commands.hrl").
muc_message_sent(Stanza, MUCState, RoomJID, FromJID, FromNick) ->
{_, RoomName, Host, _, _, _, _} = RoomJID,
OccuList = get_room_occupants(RoomName, Host),
?INFO_MSG("muc_message_sent OccuList ~p~n", [OccuList]),
Stanza.
Code for room occupants retrieval:
get_room_occupants(Room, Host) ->
case get_room_pid(Room, Host) of
room_not_found ->
?INFO_MSG("muc_message_sent get_room_occ ~p~n", [room]);
Pid -> get_room_occupants(Pid)
end.
get_room_occupants(Pid) ->
?INFO_MSG("muc_message_sent get_room_pstate ~p~n", [Pid]),
S = get_room_state(Pid),
?INFO_MSG("muc_message_sent get_room_state S ~p~n", [S]),
lists:map(
fun({_LJID, Info}) ->
{jid:to_string(Info#user.jid),
Info#user.nick,
atom_to_list(Info#user.role)}
end,
dict:to_list(S#state.users)).
%% #doc Get the Pid of an existing MUC room, or 'room_not_found'.
get_room_pid(Name, Service) ->
case mnesia:dirty_read(muc_online_room, {Name, Service}) of
[] ->
?INFO_MSG("muc_message_sent get_room_pid ~p~n", [failed]),
room_not_found;
[Room] ->
?INFO_MSG("muc_message_sent get_room_pid ~p~n", [pid]),
Room#muc_online_room.pid
end.
get_room_state(Room_pid) ->
{ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_state),
R.
The get_room_occupants code is pulled directly from mod_muc_admin where it works fine (but I couldn't find a way to directly use the functions within that module). Been stuck on this for a while now so any ideas appreciated.

Erl file not compiling due to syntax errors

I am trying to figure out what is wrong with this code, cause it is giving me errors preventing it from compiling properly into a beam file. I do not see what is wrong with the syntax. Is there an IDE which could help me out?
These are the errors:
parallels#parallels-Parallels-Virtual-Platform:/var/backend/ejabberd_modules# erlc -I /var/tmp/ejabberd/src/ mod_stanza_ack.erl
./mod_stanza_ack.erl:97: syntax error before: '.'
./mod_stanza_ack.erl:98: syntax error before: Body
./mod_stanza_ack.erl:16: function route/3 undefined
./mod_stanza_ack.erl:3: Warning: behaviour gen_mod undefined
./mod_stanza_ack.erl:111: Warning: function strip_bom/1 is unused
./mod_stanza_ack.erl:114: Warning: function send_presence/3 is unused
./mod_stanza_ack.erl:120: Warning: function echo/3 is unused
./mod_stanza_ack.erl:123: Warning: function send_message/4 is unused
This is the code:
-module(mod_stanza_ack).
-behavior(gen_server).
-behavior(gen_mod).
-export([start_link/2]).
-export([start/2,
stop/1,
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-export([route/3]).
-include("ejabberd.hrl").
-define(PROCNAME, ejabberd_mod_stanza_ack).
-define(BOTNAME, stanza_ack).
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = {Proc,
{?MODULE, start_link, [Host, Opts]},
temporary,
1000,
worker,
[?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, stop),
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
init([Host, Opts]) ->
?DEBUG("ECHO_BOT: Starting echo_bot", []),
% add a new virtual host / subdomain "echo".example.com
MyHost = gen_mod:get_opt_host(Host, Opts, "echo.#HOST#"),
ejabberd_router:register_route(MyHost, {apply, ?MODULE, route}),
{ok, Host}.
handle_call(stop, _From, Host) ->
{stop, normal, ok, Host}.
handle_cast(_Msg, Host) ->
{noreply, Host}.
handle_info(_Msg, Host) ->
{noreply, Host}.
terminate(_Reason, Host) ->
ejabberd_router:unregister_route(Host),
ok.
code_change(_OldVsn, Host, _Extra) ->
{ok, Host}.
% Checks a presence /subscription/ is a part of this.
% we may want to impliment blacklisting / some kind of
% protection here to prevent malicious users
%route(From, #jid{luser = ?BOTNAME} = To, {xmlelement, "presence", _, _} = Packet) ->
route(From, To, {xmlelement, "presence", _, _} = Packet) ->
case xml:get_tag_attr_s("type", Packet) of
"subscribe" ->
send_presence(To, From, "subscribe");
"subscribed" ->
send_presence(To, From, "subscribed"),
send_presence(To, From, "");
"unsubscribe" ->
send_presence(To, From, "unsubscribed"),
send_presence(To, From, "unsubscribe");
"unsubscribed" ->
send_presence(To, From, "unsubscribed");
"" ->
send_presence(To, From, "");
"unavailable" ->
ok;
"probe" ->
send_presence(To, From, "");
_Other ->
?INFO_MSG("Other kind of presence~n~p", [Packet])
end,
ok;
%route(From, #jid{luser = ?BOTNAME} = To, {xmlelement, "message", _, _} = Packet) ->
route(From, To, {xmlelement, "message", _, _} = Packet) ->
case xml:get_subtag_cdata(Packet, "body") of
"" ->
ok.
Body ->
case xml:get_tag_attr_s("type", Packet) of
"error" ->
?ERROR_MSG("Received error message~n~p -> ~p~n~p", [From, To, Packet]);
_ ->
echo(To, From, strip_bom(Body))
end
end,
ok.
%% HELPER FUNCTIONS
strip_bom([239,187,191|C]) -> C;
strip_bom(C) -> C.
send_presence(From, To, "") ->
ejabberd_router:route(From, To, {xmlelement, "presence", [], []});
send_presence(From, To, TypeStr) ->
ejabberd_router:route(From, To, {xmlelement, "presence", [{"type", TypeStr}], []}).
echo(From, To, Body) ->
send_message(From, To, "chat", Body).
send_message(From, To, TypeStr, BodyStr) ->
XmlBody = {xmlelement, "message",
[{"type", TypeStr},
{"from", jlib:jid_to_string(From)},
{"to", jlib:jid_to_string(To)}],
[{xmlelement, "body", [],
[{xmlcdata, BodyStr}]}]},
ejabberd_router:route(From, To, XmlBody).
./mod_stanza_ack.erl:97: syntax error before: '.' is saying the error is in line 97.
At line 97 change ok. to ok;. This will fix the issue.
Erlide plugin for eclipse is a good IDE to try out.
On line 97 in the function route/3 the first clause of the case is
"" ->
ok.
The . here ends the function which is illegal in the middle of a case, it should be a ;. This also means that the parser assumes that the variable Body on the next line starts a new function which is also illegal as a function name cannot be a variable.
Seeing the function route/3 has not been defined it cannot be exported which is the cause of the undefined function error on line 16.
Sometimes the compiler error messages are a bit cryptic but the line numbers usually help.

gen_server closing listening socket

What I'm trying to do is have a gen_server process accept a new client and immediately spawn a new child to handle the next one. The issue that I'm seeing is that when the socket is finished and consequentially terminates, it also closes the listening socket and I can't figure out why, even though it no longer references it.
Any idea what I am doing wrong?
gen_server:
-module(simple_tcp).
-behaviour(gen_server).
%% API
-export([start_link/1, stop/0, start/0, start/1]).
%% gen-server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-define(DEFAULT_PORT, 1055).
-record(state, {port, lsock}).
start_link({port, Port}) ->
gen_server:start_link(?MODULE, [{port, Port}], []);
start_link({socket, Socket}) ->
gen_server:start_link(?MODULE, [{socket, Socket}], []).
start({port, Port}) ->
simple_tcp_sup:start_child({port, Port});
start({socket, Socket}) ->
simple_tcp_sup:start_child({socket, Socket}).
start() ->
start({port, ?DEFAULT_PORT}).
stop() ->
gen_server:cast(?SERVER, stop).
% Callback functions
init([{port, Port}]) ->
{ok, LSock} = gen_tcp:listen(Port, [{active, true},{reuseaddr, true}]),
init([{socket, LSock}]);
init([{socket, Socket}]) ->
io:fwrite("Starting server with socket: ~p~n", [self()]),
{ok, Port} = inet:port(Socket),
{ok, #state{port=Port, lsock=Socket}, 0}.
handle_call(_Msg, _From, State) ->
{noreply, State}.
handle_cast(stop, State) ->
{stop, ok, State}.
handle_info({tcp, Socket, RawData}, State) ->
gen_tcp:send(Socket, io_lib:fwrite("Received raw data: ~p~n", [RawData])),
{noreply, State};
handle_info({tcp_error, _Socket, Reason}, State) ->
io:fwrite("Error: ~p~n", [Reason]),
{stop, normal, State};
handle_info(timeout, #state{lsock = LSock} = State) ->
case gen_tcp:accept(LSock) of
{ok, Sock} ->
io:fwrite("Accepting connection...~p~n", [self()]),
start({socket, LSock}),
{noreply, #state{lsock=Sock}};
{error, Reason} ->
io:fwrite("Error: ~p, ~p~n", [Reason, self()]),
{stop, normal, State}
end;
handle_info({tcp_closed, _Port}, State) ->
io:fwrite("Socket closed: ~p~n", [self()]),
simple_tcp_sup:kill_child(self()),
{stop, normal, State}.
terminate(_Reason, _State) ->
io:fwrite("Shutting down server: ~p~n", [self()]),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
supervisor:
-module(simple_tcp_sup).
-behaviour(supervisor).
-export([start_link/0,
start_child/1
]).
-export([init/1]).
-define(SERVER, ?MODULE).
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
start_child({socket, Socket}) ->
io:fwrite("Spawning child with socket...~n"),
supervisor:start_child(?SERVER, [{socket, Socket}]);
start_child({port, Port}) ->
io:fwrite("Spawning child with port...~n"),
supervisor:start_child(?SERVER, [{port, Port}]).
init([]) ->
Element = {simple_tcp, {simple_tcp, start_link, []},
temporary, brutal_kill, worker, [simple_tcp]},
Children = [Element],
RestartStrategy = {simple_one_for_one, 0, 1},
{ok, {RestartStrategy, Children}}.
Your third handle_info reverses the roles of Sock and LSock. It should pass Sock to the child process and leave its own state unmodified.
BTW: It's bad karma to rebuild State from scratch (#state{lsock=Sock}) you should always derive the new State from current State (State#state{lsock=Sock}), just in case you later add more state variables. Actually, this has a bug right now (albeit a benign one), since you are throwing away the port number.
Well, i suggest that you let the Socket stuff be handled by separate processes that communicate asynchronously with the gen_server and are linked with it. I have a sample code snippet that would show you how this could be done. The gen_server starts and spawns a TCP listener which after successfully obtaining a listening socket informs our gen_server as to change its internal state.
I have arranged the code from top down. All relevant functions have been showed.
Focus on the socket handling processes and how they interact with the gen_server
-define(PEER_CLIENT_TIMEOUT,timer:seconds(20)).
-define(PORT_RANGE,{10245,10265}).
-define(DEBUG(X,Y),error_logger:info_msg(X,Y)).
-define(ERROR(L),error_logger:error_report(L)).
-define(SOCKET_OPTS(IP),[inet,binary,{backlog,100},{packet,0},
{reuseaddr,true},{active,true},
{ip,IP}]).
%%----------------------------------------------------
%% gen_server starts here....
start(PeerName)->
gen_server:start_link({local,?MODULE},?MODULE,PeerName,[]).
%%%-------------------------------------------
%% Gen_server init/1 function
init(PeerName)->
process_flag(trap_exit,true),
%% starting the whole Socket chain below..
start_link_listener(),
%% Socket stuff started, gen_server can now wait for async
%% messages
{ok,[]}.
%%% ---- Socket handling functions ---------
%% Function: start_link_listener/0
%% Purpose: Starts the whole chain of listening
%% and waiting for connections. Executed
%% directly by the gen_server process, But
%% spawns a separate process to do the rest
start_link_listener()->
Ip_address = get_myaddr(),
spawn_link(fun() -> listener(?SOCKET_OPTS(Ip_address)) end).
%%%----------------------------------------------
%% Function: get_myaddr/0
%% Purpose: To pick the active IP address on my machine to
%% listen on
get_myaddr()->
?DEBUG("Server> Trying to extract My Local Ip Address....",[]),
{ok,Name} = inet:gethostname(),
{ok,IP} = inet:getaddr(Name,inet),
?DEBUG("Server> Found Alive Local IP address: ~p.....~n",[IP]),
IP.
%%%--------------------------------------------------
%% Function: listener/1, executed in a separate process
%% Purpose: Tries a given ?PORT_RANGE, with the given Socket Options
%% Once it acquires a ListenSocket, it will cast the gen_server!
listener(SocketOpts)->
process_flag(trap_exit,true),
Ports = lists:seq(element(1,?PORT_RANGE),element(2,?PORT_RANGE)),
case try_listening(SocketOpts,Ports) of
{ok,Port,LSocket}->
PP = proplists:get_value(ip,SocketOpts),
?MODULE:started_listener(Port,PP,LSocket),
accept_connection(LSocket);
{error,failed} -> {error,failed,SocketOpts}
end.
try_listening(_Opts,[])-> {error,failed};
try_listening(Opts,[Port|Rest])->
case gen_tcp:listen(Port,Opts) of
{ok,Listen_Socket} -> {ok,Port,Listen_Socket};
{error,_} -> try_listening(Opts,Rest)
end.
%%%---------------------------------------------------------
%% Helper Functions for Converting IP Address from tuple
%% to string and vice versa
str(X) when is_integer(X)-> integer_to_list(X).
formalise_ipaddress({A,B,C,D})->
str(A) ++ "." ++ str(B) ++ "." ++ str(C) ++ "." ++ str(D).
unformalise_address(String)->
[A,B,C,D] = string:tokens(String,"."),
{list_to_integer(A),list_to_integer(B),list_to_integer(C),list_to_integer(D)}.
%%%--------------------------------------------------
%% Function: get_source_connection/1
%% Purpose: Retrieving the IP and Port at the other
%% end of the connection
get_source_connection(Socket)->
try inet:peername(Socket) of
{ok,{IP_Address, Port}} ->
[{ipAddress,formalise_ipaddress(IP_Address)},{port,Port}];
_ -> failed_to_retrieve_address
catch
_:_ -> failed_to_retrieve_address
end.
%%%-----------------------------------------------------
%% Function: accept_connection/1
%% Purpose: waits for a connection and re-uses the
%% ListenSocket by spawning another thread
%% to take it and listen too. It casts the gen_server
%% at each connection and provides details about it.
accept_connection(ListenSocket)->
case gen_tcp:accept(ListenSocket,infinity) of
{ok, Socket}->
%% re-use the ListenSocket below.....
spawn_link(fun() -> accept_connection(ListenSocket) end),
OtherEnd = get_source_connection(Socket),
?MODULE:accepted_connection(OtherEnd),
loop(Socket,OtherEnd);
{error,_} = Reason ->
?ERROR(["Listener has failed to accept a connection",
{listener,self()},{reason,Reason}])
end.
%%%-------------------------------------------------------------------------
%% Function: loop/2
%% Purpose: TCP reception loop, it casts the gen_server
%% as soon as it receives something. gen_server
%% is responsible for generating reponse
%% OtherEnd ::= [{ipAddress,StringIPAddress},{Port,Port}] or 'failed_to_retrieve_address'
loop(Socket,OtherEnd)->
receive
{tcp, Socket, Data}->
?DEBUG("Acceptor: ~p has received a binary message from: ~p~n",[self(),OtherEnd]),
Reply = ?MODULE:incoming_binary_message(Data,OtherEnd),
gen_tcp:send(Socket,Reply),
gen_tcp:close(Socket),
exit(normal);
{tcp_closed, Socket} ->
?DEBUG("Acceptor: ~p. Socket closed by other end: ~p~n",[self(),OtherEnd]),
?MODULE:socket_closed(OtherEnd),
exit(normal);
Any -> ?DEBUG("Acceptor: ~p has received a message: ~p~n",[self(),Any])
end.
%%%----------------------------------------------
%% Gen_server Asynchronous APIs
accepted_connection(failed_to_retrieve_address)-> ok;
accepted_connection([{ipAddress,StringIPAddress},{Port,Port}])->
gen_server:cast(?MODULE,{connected,StringIPAddress,Port}).
socket_closed(failed_to_retrieve_address)-> ok;
socket_closed([{ipAddress,StringIPAddress},{Port,Port}])->
gen_server:cast(?MODULE,{socket_closed,StringIPAddress,Port}).
incoming_binary_message(Data,_OtherEnd)-> %% expecting a binary reply
case analyse_protocol(Data) of
wrong -> term_to_binary("protocol violation!");
Val -> gen_server:call(?MODULE,{request,Val},infinity)
end.
%%% -------------------- handle cast ------------------------------------------
handle_cast({listener_starts,_Port,_MyTupleIP,_LSocket} = Object,State)->
NewState = do_something_with_the_listen_report(Object),
{noreply,NewState};
handle_cast({connected,_StringIPAddress,_Port} = Object,State)->
NewState = do_something_with_the_connection_report(Object),
{noreply,NewState};
handle_cast({socket_closed,_StringIPAddress,_Port} = Object,State)->
NewState = do_something_with_the_closed_connection_report(Object),
{noreply,NewState};
handle_cast(Any,State)->
?DEBUG("Server> I have been casted some unknown message: ~p~n",[Any]),
{noreply,State}.
%%%% ---------------------- handle call --------------
handle_call({request,Val},_,State)->
{NewState,Reply} = req(Val,State),
{reply,Reply,NewState};
handle_call(_,_,State)-> {reply,[],State}.
req(Val,State)->
%% modify gen_server state and
%% build reply
{NewState,Reply} = modify_state_and_get_reply(State,Val),
{NewState,Reply}.
%%------------------- terminate/2 --------------------
terminate(_Reason,_State)-> ok.
%%----------------- code_change/3 ------------------
code_change(_,State,_)-> {ok,State}.
With the asynchronous capability of the gen_server, we can handle the socket details from separate linked processes. These processes then would communicate with the gen_server via cast and without blocking the gen_server from its concurrent nature.

Resources