Non-blocking TCP server using OTP principles

前端 未结 3 1354
名媛妹妹
名媛妹妹 2021-02-06 05:39

I\'m starting to learn Erlang, so I\'m trying to write the \"hello, world!\" of concurrent programming, an IRC bot.

I\'ve already written one using Erlang without any O

3条回答
  •  悲&欢浪女
    2021-02-06 06:15

    Great that you've began learning Erlang/OTP!

    The following resources are very useful:

    • The OTP Design Principles. Read this carefully, if you already haven't. Note the common misconception that OTP is object orientated (OO): it's not! Forget everything about "inheritance". It's not possible to merely build complete systems by "extending" standard modules.
    • The Messaging System:

      These functions must be used to implement the use of system messages for a process

    • The Special Processes. A special process is an OTP-compliant process that can integrate well with supervisors.

    This is some code I have in my project. I am an Erlang learner too, so don't 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}.
    

    Note that this is a compliant OTP process (it can be managed by a supervisor). You should use AcceptFun to spawn (=faster) a new worker child. I have not yet tested it thorough though.

    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>'s ok I pointed my Google Chrome browser to port 8080: a great test for TCP!)

提交回复
热议问题