2011-05-29 106 views
7

我開始學習Erlang,所以我試圖寫下「hello,world!」併發編程,IRC bot。使用OTP原理的非阻塞TCP服務器

我已經寫了一個使用Erlang而沒有任何OTP細節(監督者,應用程序等行爲)。我正在使用OTP原則來重寫它,但不幸的是我無法弄清楚使用OTP進行套接字編程的「正確」方式。

似乎唯一合理的方法是手動創建另一個進程並將其鏈接到主管,但肯定某個人在某處已經完成了此操作。

回答

3

我認爲這是你在找什麼: 它是關於如何建立使用OTP非阻塞TCP服務器的完全手冊(當然,是完全記錄和解釋)。

+4

不使用,使用未記錄(可能不穩定)的prim_inet:async_accept/2。也許這裏沒有「OTP方法」:/ – rpkelly 2011-05-31 02:18:48

+1

在這種情況下,我會簡單地使用gen_tcp:accept/1和gen_tcp:controlling_process/2(如文檔所示:「將新的控制進程Pid分配給Socket控制過程是從套接字接收消息的過程。如果被當前控制進程以外的任何其他進程調用,則返回{error,eperm}。 「)。這是一個如何使用它的例子:http://20bits.com/articles/erlang-a-generalized-tcp-server/(請注意以下段落:」使用gen_server實現網絡服務器的問題是那麼調用gen_tcp:accept ...「。希望這有幫助 – Alin 2011-05-31 06:46:53

1

很好,你已經開始學習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我指着我的谷歌Chrome瀏覽器的8080端口:一個極大的考驗了TCP)

+0

順便說一下,使用這個OTP進程,任何管理員的shutdown參數都是有意義的:它應該至少大於ACCEPT_TIMEOUT。 – Pindatjuh 2011-06-28 22:39:54

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。