Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Well, i suggest that you let the Socket stuff be handled by separate processes that communicate asynchronously with the gen_server and are <code>linked</code> 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</p> <pre> -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)-&gt; gen_server:start_link({local,?MODULE},?MODULE,PeerName,[]). %%%------------------------------------------- %% Gen_server init/1 function init(PeerName)-&gt; 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()-&gt; Ip_address = get_myaddr(), spawn_link(fun() -&gt; 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()-&gt; ?DEBUG("Server&gt; Trying to extract My Local Ip Address....",[]), {ok,Name} = inet:gethostname(), {ok,IP} = inet:getaddr(Name,inet), ?DEBUG("Server&gt; 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)-&gt; 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}-&gt; PP = proplists:get_value(ip,SocketOpts), ?MODULE:started_listener(Port,PP,LSocket), accept_connection(LSocket); {error,failed} -&gt; {error,failed,SocketOpts} end. try_listening(_Opts,[])-&gt; {error,failed}; try_listening(Opts,[Port|Rest])-&gt; case gen_tcp:listen(Port,Opts) of {ok,Listen_Socket} -&gt; {ok,Port,Listen_Socket}; {error,_} -&gt; try_listening(Opts,Rest) end. %%%--------------------------------------------------------- %% Helper Functions for Converting IP Address from tuple %% to string and vice versa str(X) when is_integer(X)-&gt; integer_to_list(X). formalise_ipaddress({A,B,C,D})-&gt; str(A) ++ "." ++ str(B) ++ "." ++ str(C) ++ "." ++ str(D). unformalise_address(String)-&gt; [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)-&gt; try inet:peername(Socket) of {ok,{IP_Address, Port}} -&gt; [{ipAddress,formalise_ipaddress(IP_Address)},{port,Port}]; _ -&gt; failed_to_retrieve_address catch _:_ -&gt; 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)-&gt; case gen_tcp:accept(ListenSocket,infinity) of {ok, Socket}-&gt; %% re-use the ListenSocket below..... spawn_link(fun() -&gt; accept_connection(ListenSocket) end), OtherEnd = get_source_connection(Socket), ?MODULE:accepted_connection(OtherEnd), loop(Socket,OtherEnd); {error,_} = Reason -&gt; ?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)-&gt; receive {tcp, Socket, Data}-&gt; ?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} -&gt; ?DEBUG("Acceptor: ~p. Socket closed by other end: ~p~n",[self(),OtherEnd]), ?MODULE:socket_closed(OtherEnd), exit(normal); Any -&gt; ?DEBUG("Acceptor: ~p has received a message: ~p~n",[self(),Any]) end. %%%---------------------------------------------- %% Gen_server Asynchronous APIs accepted_connection(failed_to_retrieve_address)-&gt; ok; accepted_connection([{ipAddress,StringIPAddress},{Port,Port}])-&gt; gen_server:cast(?MODULE,{connected,StringIPAddress,Port}). socket_closed(failed_to_retrieve_address)-&gt; ok; socket_closed([{ipAddress,StringIPAddress},{Port,Port}])-&gt; gen_server:cast(?MODULE,{socket_closed,StringIPAddress,Port}). incoming_binary_message(Data,_OtherEnd)-&gt; %% expecting a binary reply case analyse_protocol(Data) of wrong -&gt; term_to_binary("protocol violation!"); Val -&gt; gen_server:call(?MODULE,{request,Val},infinity) end. %%% -------------------- handle cast ------------------------------------------ handle_cast({listener_starts,_Port,_MyTupleIP,_LSocket} = Object,State)-&gt; NewState = do_something_with_the_listen_report(Object), {noreply,NewState}; handle_cast({connected,_StringIPAddress,_Port} = Object,State)-&gt; NewState = do_something_with_the_connection_report(Object), {noreply,NewState}; handle_cast({socket_closed,_StringIPAddress,_Port} = Object,State)-&gt; NewState = do_something_with_the_closed_connection_report(Object), {noreply,NewState}; handle_cast(Any,State)-&gt; ?DEBUG("Server&gt; I have been casted some unknown message: ~p~n",[Any]), {noreply,State}. %%%% ---------------------- handle call -------------- handle_call({request,Val},_,State)-&gt; {NewState,Reply} = req(Val,State), {reply,Reply,NewState}; handle_call(_,_,State)-&gt; {reply,[],State}. req(Val,State)-&gt; %% modify gen_server state and %% build reply {NewState,Reply} = modify_state_and_get_reply(State,Val), {NewState,Reply}. %%------------------- terminate/2 -------------------- terminate(_Reason,_State)-&gt; ok. %%----------------- code_change/3 ------------------ code_change(_,State,_)-&gt; {ok,State}. </pre> <p>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 <code>cast</code> and without blocking the gen_server from its concurrent nature.</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload