2011-05-29 11 views
7

Estoy empezando a aprender Erlang, así que estoy tratando de escribir el "¡Hola, mundo!" de programación concurrente, un bot de IRC.Servidor TCP sin bloqueo que usa los principios de OTP

Ya he escrito uno usando Erlang sin ningún tipo de detalles OTP (supervisor, aplicación, etc. comportamientos). Estoy buscando reescribirlo usando los principios de OTP, pero desafortunadamente no puedo encontrar la manera "correcta" de hacer la programación de socket con OTP.

Parece que la única forma razonable es crear otro proceso manualmente y vincularlo con el supervisor, pero seguramente alguien, en alguna parte, ya lo ha hecho antes.

Respuesta

3

creo que esto es lo que está buscando: Es un completo tutorial sobre cómo construir un servidor TCP no bloqueante usando OTP (por supuesto, está totalmente documentado y explicado).

+4

No, usa el prim_inet no documentado (y potencialmente inestable): async_accept/2. Tal vez no hay una "forma OTP" para hacer esto:/ – rpkelly

+1

En este caso, simplemente usaría gen_tcp: accept/1 y gen_tcp: controlling_process/2 (como sugiere la documentación: "Asigna un nuevo proceso de control de Pid a Socket El proceso de control es el proceso que recibe mensajes del socket.Si es llamado por cualquier otro proceso que no sea el proceso de control actual, se devuelve {error, eperm}. "). Este es un ejemplo de cómo usar esto: http://20bits.com/articles/erlang-a-generalized-tcp-server/ (observe el siguiente párrafo:" El problema con la implementación de un servidor de red usando gen_server es que la llamada a gen_tcp: aceptar ... ". Espero que esto ayude. – Alin

1

¡Excelente que hayas empezado a aprender Erlang/OTP!

Los siguientes recursos son muy útiles:

  • El OTP Design Principles. Lea esto cuidadosamente, si ya no lo hizo. Tenga en cuenta la idea errónea de que OTP está orientado a objetos (OO): ¡no lo es! Olvídate de todo sobre "herencia". No es posible simplemente construir sistemas completos "extendiendo" módulos estándar.
  • El Messaging System:

    Estas funciones deben ser utilizados para implementar el uso de mensajes del sistema para un proceso de

  • El Special Processes. Un proceso especial es un proceso compatible con OTP que puede integrarse bien con los supervisores.

Este es un código que tengo en mi proyecto. También soy aprendiz de Erlang, así que no confíes demasiado en el código, por favor.

-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}. 

Tenga en cuenta que este es un proceso OTP compatible (que puede ser manejado por un supervisor). Debe usar AcceptFun para generar (= más rápido) un nuevo hijo trabajador. Aún no lo he probado a fondo.

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> 

(Después 2> 's ok me señaló mi navegador Google Chrome con el puerto 8080: una gran prueba para TCP)

+0

Por cierto, usando este proceso OTP, el parámetro' shutdown' para cualquier supervisor tiene sentido: al menos debería ser mayor que 'ACCEPT_TIMEOUT'. – Pindatjuh

1

Otra forma de implementar un oyente TCP asíncrono es mediante el uso supervisor_bridge.

Aquí hay un código que escribí para mostrar esto (no probado):

-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. 

mucho más fácil de entender que mi otra respuesta. El connection_bridge se puede ampliar para admitir también UDP y SCTP.

Cuestiones relacionadas