Non-blocking TCP server using OTP principles

I'm starting to learn Erlang, so I'm trying to write "hello world!". parallel programming, IRC bot.

I already wrote one using Erlang without any OTP subtleties (supervisor behavior, applications, etc.). I am looking to rewrite it using OTP principles, but unfortunately I cannot figure out the β€œright” way to program sockets using OTP.

It seems the only sensible way is to create another process manually and associate it with the supervisor, but, of course, someone has done this before.

+7
source share
3 answers

I think this is what you are looking for: http://www.trapexit.org/Building_a_Non-blocking_TCP_server_using_OTP_principles This is a complete tutorial on how to create a non-blocking TCP server using OTP (of course, fully documented and explained).

+3
source

Great that you started to learn Erlang / OTP!

The following resources are useful:

  • OTP Design Principles . Read this carefully if you have not already done so. Note the common misconception that OTP is object oriented (OO): no! Forget everything about inheritance. It is not possible to simply build complete systems by β€œextending” standard modules.
  • Messaging system :

    These functions should be used to implement the use of system messages for the process.

  • Special processes . A particular process is an OTP-compatible process that can integrate well with supervisors.

This is the code that I have in my project. I also participate in Erlang, so do not trust the code too much, please.

-module(gen_tcpserver). %% Public API -export([start_link/2]). %% start_link reference -export([init/2]). %% System internal API -export([system_continue/3, system_terminate/4, system_code_change/4]). -define(ACCEPT_TIMEOUT, 250). -record(server_state, {socket=undefined, args, func}). %% ListenArgs are given to gen_tcp:listen %% AcceptFun(Socket) -> ok, blocks the TCP accept loop start_link(ListenArgs, AcceptFun) -> State = #server_state{args=ListenArgs,func=AcceptFun}, proc_lib:start_link(?MODULE, init, [self(), State]). init(Parent, State) -> {Port, Options} = State#server_state.args, {ok, ListenSocket} = gen_tcp:listen(Port, Options), NewState = State#server_state{socket=ListenSocket}, Debug = sys:debug_options([]), proc_lib:init_ack(Parent, {ok, self()}), loop(Parent, Debug, NewState). loop(Parent, Debug, State) -> case gen_tcp:accept(State#server_state.socket, ?ACCEPT_TIMEOUT) of {ok, Socket} when Debug =:= [] -> ok = (State#server_state.func)(Socket); {ok, Socket} -> sys:handle_debug(Debug, fun print_event/3, undefined, {accepted, Socket}), ok = (State#server_state.func)(Socket); {error, timeout} -> ok; {error, closed} when Debug =:= [] -> sys:handle_debug(Debug, fun print_event/3, undefined, {closed}), exit(normal); {error, closed} -> exit(normal) end, flush(Parent, Debug, State). flush(Parent, Debug, State) -> receive {system, From, Msg} -> sys:handle_system_msg(Msg, From, Parent, ?MODULE, Debug, State) after 0 -> loop(Parent, Debug, State) end. print_event(Device, Event, _Extra) -> io:format(Device, "*DBG* TCP event = ~p~n", [Event]). system_continue(Parent, Debug, State) -> loop(Parent, Debug, State). system_terminate(Reason, _Parent, _Debug, State) -> gen_tcp:close(State#server_state.socket), exit(Reason). system_code_change(State, _Module, _OldVsn, _Extra) -> {ok, State}. 

Please note that this is a compatible OTP process (it can be controlled by a supervisor). You must use AcceptFun to create (= faster) a new work child. I have not tested it yet.

 1> {ok, A} = gen_tcpserver:start_link({8080,[]},fun(Socket)->gen_tcp:close(Socket) end). {ok,<0.93.0>} 2> sys:trace(A, true). ok *DBG* TCP event = {accepted,#Port<0.2102>} *DBG* TCP event = {accepted,#Port<0.2103>} 3> 

(After 2> ok I pointed the Google Chrome browser to port 8080: a great test for TCP!)

+1
source

Another way to implement an asynchronous TCP listener is supervisor_bridge .

Here is the code I wrote to show this (not verified):

 -module(connection_bridge). -behaviour(supervisor_bridge). % supervisor_bridge export -export([init/1, terminate/2]). % internal proc_lib:start_link -export([accept_init/3]). %% Port: see gen_tcp:listen(Port, _). %% Options: see gen_tcp:listen(_, Options). %% ConnectionHandler: Module:Function(Arguments)->pid() or fun/0->pid() %% ConnectionHandler: return pid that will receive TCP messages init({Port, Options, ConnectionHandler}) -> case gen_tcp:listen(Port, Options) of {ok, ListenSocket} -> {ok, ServerPid} = proc_lib:start_link(?MODULE, accept_init, [self(), ListenSocket, ConnectionHandler], 1000), {ok, ServerPid, ListenSocket}; OtherResult -> OtherResult end. terminate(_Reason, ListenSocket) -> gen_tcp:close(ListenSocket). accept_init(ParentPid, ListenSocket, ConnectionHandler) -> proc_lib:init_ack(ParentPid, {ok, self()}), accept_loop(ListenSocket, ConnectionHandler). accept_loop(ListenSocket, ConnectionHandler) -> case gen_tcp:accept(ListenSocket) of {ok, ClientSocket} -> Pid = case ConnectionHandler of {Module, Function, Arguments} -> apply(Module, Function, Arguments); Function when is_function(Function, 0) -> Function() end, ok = gen_tcp:controlling_process(ClientSocket, Pid), accept_loop(ListenSocket, ConnectionHandler); {error, closed} -> error({shutdown, tcp_closed}); {error, Reason} -> error(Reason) end. 

Much easier to understand than my other answer. connection_bridge can be expanded to support UDP and SCTP.

+1
source

All Articles