使用OTP原则的非阻塞TCP服务器

时间:2011-05-29 21:44:29

标签: tcp erlang otp

我开始学习Erlang,所以我想写下“你好,世界!”并发编程,一个IRC机器人。

我已经使用Erlang编写了一个没有任何OTP细节(主管,应用程序等行为)。我想用OTP原理重写它,但不幸的是我无法弄清楚使用OTP进行套接字编程的“正确”方法。

似乎唯一合理的方法是手动创建另一个进程并将其链接到主管,但肯定有人在某个地方之前已经这样做了。

3 个答案:

答案 0 :(得分:4)

我认为这就是你要找的东西: http://www.trapexit.org/Building_a_Non-blocking_TCP_server_using_OTP_principles 这是一个关于如何使用OTP构建非阻塞TCP服务器的完整教程(当然,有完整的文档和解释)。

答案 1 :(得分:3)

很高兴你开始学习Erlang / OTP了!

以下资源非常有用:

  • OTP Design Principles。如果您还没有,请仔细阅读。注意OTP面向对象(OO)的常见误解:它不是!忘记关于“继承”的一切。仅通过“扩展”标准模块来构建完整的系统是不可能的。
  • Messaging System
      

    这些功能必须用于实现过程系统消息的使用

  •   
  • Special Processes。特殊流程是符合OTP标准的流程,可以与主管良好集成。
  •   

这是我项目中的一些代码。我也是Erlang学习者,所以请不要太信任代码。

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

请注意,这是一个符合标准的OTP流程(可以由主管管理)。您应该使用AcceptFun来生成(=更快)一个新的工人孩子。我还没有彻底测试过它。

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> 

2>之后ok我将我的Google Chrome浏览器指向端口8080:对TCP的一个很好的测试!)

答案 2 :(得分:1)

实现异步TCP侦听器的另一种方法是使用supervisor_bridge

以下是我编写的一些代码(未经测试):

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

比我的其他答案更容易理解。 connection_bridge可以扩展为支持UDP和SCTP。