how can I use Erlang/OTP gen_statem for a distributed chat server - erlang

I am developing a chat server and it is distributed. I can spawn new users (using start_link) in multiple nodes and can send a message to a node, then received message will be shown on received node. I have no idea how should I use gen_statem(FSM) for this application. I need to add a FSM between two chat users(nodes). I have used a record to save states of sender, receiver and message also. Could someone suggest me how to add gen_statem here ?
I need to visualize the states received, sent respectively for send_message and receive message events.
chat server:
-module(message_server).
-behaviour(gen_server).
-export([send_message/2, receive_message/3, start_link/1, stop/0, receive_db_data/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3]).
-define(SERVER, ?MODULE).
-record(message_server_state, {from, to, msgsent, msgreceived}).
%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================
start_link(Name) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, Name, []).
init(Name) ->
io:format("~p is connected with the server...~n", [Name]),
{ok, #message_server_state{
from = node(),
to = [],
msgsent = [],
msgreceived = []
}}.
stop() ->
gen_server:stop(?MODULE).
receive_db_data(Sender) ->
database_server:getalldb(Sender).
send_message(To, Msg) ->
gen_server:call({?MODULE, node()}, {send, To, Msg}).
receive_message(Sender, To, Msg) ->
gen_server:call({?MODULE, list_to_atom(To)}, {reciv, Sender, Msg}).
handle_call({send, To, Msg}, _From, State = #message_server_state{to = Receivers, msgsent = Msgsent, from = Sender}) ->
io:format("Sent message: ~p~n", [Msg]),
receive_message(Sender, To, Msg),
{reply, ok, State#message_server_state{msgsent = [Msg | Msgsent], to = [To | Receivers]}};
handle_call({reciv, Sender, Msg}, _From, State = #message_server_state{msgreceived = Msgreceived}) ->
io:format("Sent by: ~p~n", [Sender]),
io:format("Message: ~p~n", [Msg]),
receive_db_data(Sender),
{reply, ok, State#message_server_state{msgreceived = [Msg | Msgreceived]}}.
handle_cast(_Request, State = #message_server_state{}) ->
{noreply, State}.
handle_info(_Info, State = #message_server_state{}) ->
{noreply, State}.
terminate(_Reason, _State = #message_server_state{}) ->
ok.
code_change(_OldVsn, State = #message_server_state{}, _Extra) ->
{ok, State}.

Related

Returning State from a gen_server cast

I am following http://learnyousomeerlang.com/static/erlang/kitty_gen_server.erl .
I have my application logic inside of temple.erl. All of this code is tested & runs as I expect it to. My land.erl is intended to be the server that contains the Temple.
My understanding (please correct any ignorance) is that I can use gen_server to abstract message passing and keep track of state with minimal overhead.
I understand that the second tuplevalue in my init function
init([]) -> {ok, temple:new()}. is my server's state - in this case, the return value of temple:new().
So I c(land). (code below), and try this:
19> {ok, Pid2} = land:start_link().
{ok,<0.108.0>}
20> land:join(Pid2, a).
ok
and I just get the atom ok back when I send the join message From reading the code and comparing my experiences running the kitty_gen_server, I think the state is updated correctly with the value temple:join(Temple, Name), but the ok atom is the response value from
handle_call({join, Name}, _From, Temple) ->
{reply, ok, temple:join(Temple, Name)};
how can I update my state with temple:join(Temple, Name), and then return this value to the client? I don't want to call the same function twice eg.
handle_call({join, Name}, _From, Temple) ->
{reply, temple:join(Temple, Name), temple:join(Temple, Name)};
So looking at the kitty_gen_server I tried
handle_call({join, Name}, _From, Temple) ->
[{reply, JoinedTemple, JoinedTemple} || JoinedTemple <- temple:join(Temple, Name)];
and I get a function clause crash when I try this with a message about syntax error ||, and then I see this is only for list comprehensions..
how can I compute the value of temple:join(Temple, Name)] and return to the caller of land:join and update the Land's state?
-module(land).
-behaviour(gen_server).
-export([start_link/0, join/2, input/3, fight/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
start_link() ->
gen_server:start_link(?MODULE, [], []).
join(Pid, Name) ->
gen_server:call(Pid, {join, Name}).
input(Pid, Action, Target) ->
gen_server:call(Pid, {input, Action, Target}).
fight(Pid) ->
gen_server:call(Pid, fight).
init([]) -> {ok, temple:new()}.
handle_call({join, Name}, _From) ->
{reply, ok, temple:join(Name)}.
handle_call({join, Name}, _From, Temple) ->
{reply, temple:join(Temple, Name), temple:join(Temple, Name)};
handle_call(terminate, _From, Temple) ->
{stop, normal, ok, Temple}.
handle_info(Msg, Temple) ->
io:format("Unexpected message: ~p~n",[Msg]),
{noreply, Temple }.
terminate(normal, Temple) ->
io:format("Temple bathed in blood.~p~n", [Temple]),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
handle_cast(_, Temple) ->
{noreply, Temple}.
You can store the new state in a variable and then return a tuple containing that variable twice:
handle_call({join, Name}, _From, Temple) ->
NewTemple = temple:join(Temple, Name),
{reply, NewTemple, NewTemple};

About a client for pusher written in Erlang

I want to write a module which need to be able to push message to pusher in Erlang. I found this repo https://github.com/bradfordw/pusherl, which is not maintained anymore, but I think I can adapt to make it work. I followed the README, and I ran successfully all the Rebar command. Then I open the rebar console by the command
./rel/pusherl/bin/pusherl console
So the server should start. But then when I try to call
gen_server:call(pusherl_server, {push, {"ChannelName", "EventName", "Payload"}}).
Then it throws error:
exception exit: {noproc,{gen_server,call,
[pusherl_server,
{push,
{"ChannelName","EventName","Payload"}}]}}
in function gen_server:call/2 (gen_server.erl, line 182)
I'm quite new with Erlang and OTP, and it takes me half of the day to make it work but not successful. Please help me to solve this. I really appreciate.
By the way, if you know any other pusher client, please suggest me. Thanks a lot.
Here is the code to for gen_server callback:
-module(pusherl_server).
-behaviour(gen_server).
-define(SERVER, ?MODULE).
-define(JP, fun(K,V) -> string:join([K,V],"=") end).
-record(state,{app_id, key, secret}).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
init(_) ->
{ok, PusherAppId} = application:get_env(pusher_app_id),
{ok, PusherKey} = application:get_env(pusher_key),
{ok, PusherSecret} = application:get_env(pusher_secret),
{ok, #state{app_id=PusherAppId, key=PusherKey, secret=PusherSecret}}.
handle_call({push, {ChannelName, EventName, Payload}}, _From, State) ->
case http_request(ChannelName, EventName, Payload, State) of
{ok, _} -> {reply, ok, State};
{error, _} -> {reply, error, State}
end;
handle_call(_Request, _From, State) ->
{noreply, ok, State}.
handle_cast({push, {ChannelName, EventName, Payload}}, State) ->
case http_request(ChannelName, EventName, Payload, State) of
{ok, _} -> {noreply, ok, State};
{error, _} -> {noreply, error, State}
end;
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
http_request(ChannelName, EventName, Payload, Config) when is_list(ChannelName), is_record(Config, state) ->
{ok, ReqProps} = http_request_props(Payload, EventName, ChannelName, Config),
httpc:request(post, ReqProps, [], []).
http_request_props(Payload, EventName, ChannelName, #state{app_id=AppId, key=AppKey, secret=AppSecret}) ->
Md5String = lists:flatten([io_lib:format("~2.16.0b",[N]) || <<N>> <= crypto:md5(Payload)]),
ToSign = ["POST",
lists:flatten(["/apps/", AppId, "/channels/", ChannelName, "/events"]),
string:join([?JP("auth_key", AppKey),
?JP("auth_timestamp", get_time_as_string()),
?JP("auth_version", "1.0"),
?JP("body_md5", Md5String),
?JP("name", EventName)
],"&")
],
AuthSignature = signed_params(ToSign, AppSecret),
QueryParams = [
?JP("auth_key", AppKey),
?JP("auth_timestamp", get_time_as_string()),
?JP("auth_version","1.0"),
?JP("body_md5", Md5String),
?JP("auth_signature", AuthSignature),
?JP("name", EventName)
],
Url = http_api_url(AppId, ChannelName, QueryParams),
{ok, {Url, [], "application/x-www-form-urlencoded", Payload}}.
http_api_url(AppId, ChannelName, QueryParams) ->
QueryString = string:join(QueryParams,"&"),
lists:flatten(["http://api.pusherapp.com/apps/",AppId,"/channels/",ChannelName,"/events?", QueryString]).
get_time_as_string() ->
{M, S, _} = now(),
integer_to_list(((M * 1000000) + S)).
signed_params(Params, Secret) ->
lists:flatten([io_lib:format("~2.16.0b",[N]) || <<N:8>> <= sha2:hmac_sha256(Secret, string:join(Params,"\n"))]).
noproc means that the process you're trying to call is not running. You need to start it with:
pusherl_server:start_link().
Note that this will link the pusherl_server process to the calling process. If you're running this in the shell, and then do something that causes an error, the error will propagate across the link to the pusherl_server process and kill it, so then you have to start it again.
To avoid that, you can either unlink the process after starting it:
{ok, Pid} = pusherl_server:start_link().
unlink(Pid).
or add a start function to the module, that does the same as start_link except it calls gen_server:start instead of gen_server:start_link.

Erlang: list all records

Please help me, (btw my English is not very good). I want to list all records that have been created for a gen_server, for example, if a had the following code:
-module(mod).
-record(person, {name, phone}).
start_link(Param1, Param2) ->
gen_server:start_link(?MODULE, [Param1, Param2], []).
init([Param1, Param2]) ->
{ok, #person{name=Param1,phone=Param2}}.
I can called:
mod:start_link(Maria,22222).
mod:start_link(Jose,9348).
mod:start_link(lol,232).
After that i want to see all the records that i have created and put it on a list. How can i do this?
You should read this doc: http://www.erlang.org/doc/man/gen_server.html#start_link-3.
The start_link/3 just start the gen_server, do store the record data.
-module(record_make).
-compile(export_all).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-behaviour(gen_server).
-define(SERVER, ?MODULE).
-record(state, {persons = []}).
-record(person, {name, phone}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
init([]) ->
{ok, #state{}}.
handle_call({create, Name, Phone}, _Frome, State = #state{persons = Persons}) ->
NewPerson = #person{name = Name, phone = Phone},
{reply, ok, State#state{persons = [NewPerson | Persons]}};
handle_call(list, _From, State = #state{persons = Persons}) ->
io:format("~p~n", [Persons]),
{reply, ok, State};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
create(Name, Phone) ->
gen_server:call(?MODULE, {create, Name, Phone}).
list() ->
gen_server:call(?MODULE, list).
You can do like this:
1> c(record_make)
1> .
{ok,record_make}
2> record_make:start_link().
{ok,<0.41.0>}
3> record_make:create("yang", "123").
ok
4> record_make:create("zhang", "456").
ok
5> record_make:list().
[{person,"zhang","456"},{person,"yang","123"}]
ok
6>
No chance to get the information in this way: there is one record per gen_server and the information is lost as soon as the sever die. If you need this information you shold add a dedicated process to keep the information ({Pid,Record} or record number ...). The information can be updated during the init and the terminate function via function such as record_manager:create/1 and record_manager:delete/1. You can also store them in a shared ets.

How do I pass a string to epp_dodger?

I'm writing something to process Erlang source code. Pretty much the first line of the program is:
{ok, Forms} = epp_dodger:parse_file(Filename)
However, I want to do some simple unit testing. So: how do I persuade epp_dodger to take its input from a string instead of a file?
Alternatively, it has epp_dodger:parse_form/2,3, which takes an IODevice, so how do I provide an IODevice over a string?
The code below (which is admittedly a bit hackish) starts a gen_server that takes a string as an argument, and then fulfills the Erlang I/O Protocol enough to satisfy epp_dodger:parse/2 to be able to read and parse the string from that process.
Here's an example of using it:
1> {ok,P} = iostr:start("-module(x).\n-export([f/0]).\nf() -> ok.\n").
2> epp_dodger:parse(P,1).
{ok,[{tree,attribute,
{attr,1,[],none},
{attribute,
{tree,atom,{attr,1,[],none},module},
[{tree,atom,{attr,1,[],none},x}]}},
{tree,attribute,
{attr,2,[],none},
{attribute,
{tree,atom,{attr,2,[],none},export},
[{tree,list,
{attr,2,[],none},
{list,
[{tree,arity_qualifier,
{attr,2,[],none},
{arity_qualifier,
{tree,atom,{attr,...},f},
{tree,integer,{...},...}}}],
none}}]}},
{tree,function,
{attr,3,[],none},
{func,
{tree,atom,{attr,3,[],none},f},
[{tree,clause,
{attr,3,[],none},
{clause,[],none,[{atom,3,ok}]}}]}}]}
The process dies once the string is exhausted.
Note that the code probably misses a few things — handling unicode properly, for example — but it should give you a good idea of how to make a more robust I/O server for this purpose if needed.
-module(iostr).
-behaviour(gen_server).
-export([start_link/1, start/1, stop/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {
data,
line = 1,
lines
}).
start_link(Data) ->
gen_server:start_link(?MODULE, [Data], []).
start(Data) ->
gen_server:start(?MODULE, [Data], []).
stop(Pid) ->
gen_server:cast(Pid, stop).
init([Data0]) ->
Data = [Line++"\n" || Line <- string:tokens(Data0, "\n")],
{ok, #state{data=Data,lines=length(Data)}}.
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(stop, State) ->
{stop, normal, State};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({io_request,From,ReplyAs,{get_until,_,_,_,_,_}},
#state{data=[],lines=L}=State) ->
From ! {io_reply, ReplyAs, {eof,L}},
{stop, normal, State};
handle_info({io_request,From,ReplyAs,{get_until,_,_,M,F,Args}},
#state{data=Data,line=L}=State) ->
case handler(Data,L,[],M,F,Args) of
eof ->
Lines = State#state.lines,
From ! {io_reply, ReplyAs, {eof,Lines}},
{stop, normal, State#state{data=[]}};
{ok,Result,Rest,NData,NL} ->
From ! {io_reply, ReplyAs, Result},
case Rest of
[] ->
{noreply, State#state{data=NData,line=NL}};
_ ->
{noreply, State#state{data=[Rest|NData],line=NL}}
end
end;
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
handler([Input|Data],L,Cont,M,F,Extra) ->
case catch apply(M,F,[Cont,Input|Extra]) of
{done,eof,_} ->
eof;
{done,Result,Rest} ->
{ok,Result,Rest,Data,L+1};
{more,NCont} ->
case Data of
[] -> eof;
_ -> handler(Data,L+1,NCont,M,F,Extra)
end
end.

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