Errlang:汎用ネットワークサーバ


原文:
Errlang:A Generaalized TCP Server
前のいくつかの文章の中でエリンランドのことに触れました。
gen_tcpネットワークプログラミングとエリック/OPTの
gen_serverモジュールです。この二つを結びつけます。
ほとんどの人が「サーバ」というのはネットサーバを意味すると思っていますが、エリンランドがこの用語を使う時に表現したのはより抽象的な意味です。
gen_sererはErrlangでメッセージ伝達プロトコルに基づいて動作するサーバです。これに基づいてTCPサーバを接ぎ木することができますが、これはいくつかの作業が必要です。
ネットワークサーバの構成
ほとんどのネットワークサーバは似たようなアーキテクチャを持っている。
まず、それらは受信された接続を傍受するために傍受socketを作成する。
そして、それらは受信状態に入り、ここで新しい接続を循環的に受信し、終了するまで(接続が到着したことを示し、本格的なclient/server作業を開始する)。
まず前のネットワークプログラミングの中のecho serverの例を見てみます。

-module(echo).
-author('Jesse E.I. Farmer <[email protected]>').
-export([listen/1]).

-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).

% Call echo:listen(Port) to start the service.
listen(Port) ->
    {ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
    accept(LSocket).

% Wait for incoming connections and spawn the echo loop when we get one.
accept(LSocket) ->
    {ok, Socket} = gen_tcp:accept(LSocket),
    spawn(fun() -> loop(Socket) end),
    accept(LSocket).

% Echo back whatever data we receive on Socket.
loop(Socket) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Data} ->
            gen_tcp:send(Socket, Data),
            loop(Socket);
        {error, closed} ->
            ok
    end.
listenはすぐにacceptを呼び出すモニターを作成します。
acceptは、接続を待って、新しいワーカーを作成します。本当の仕事を処理して、次の接続を待ちます。
この部分のコードの中で、父プロセスはlisten socketとaccept loopの両方を持っています。
後で私達はaccept/listen loopとgen_を集成すれば見られます。serverならこうするのはよくないです。
抽象ネットワークサーバ
ネットワークサーバには2つの部分があります。接続処理と業務ロジックです。
上記のように、接続処理はネットワークサーバごとにほぼ同じです。
理想的な状態で私たちはこうしてもいいです。

-module(my_server).
start(Port) ->
  connection_handler:start(my_server, Port, businees_logic).

business_logic(Socket) ->
  % Read data from the network socket and do our thang!
これを完成し続けましょう。
汎用ネットワークサーバを実現
gen_を使うserverがネットワークサーバを実現するために問題があるのは、gen_tcp:accept呼び出しは渋滞しています。
もし私たちがサーバーの初期化ルーチンでそれを呼び出すと、全体のgen_serverメカニズムはクライアントが接続を確立するまで詰まります。
この問題を避けにくる二つの方法があります。
一つの方法は、低レベルの接続機構を使用して、非詰まり(または非同期)acceptをサポートすることである。
いろいろな方法でサポートしていますが、一番気になるのはgen_です。tcp:controllling_processは、クライアントが接続を確立したときに誰がどのようなメッセージを受け取ったかを管理してくれます。
もう一つは簡単で優雅な方法だと思います。
単独プロセスでsocketをモニターします。
プロセスは2つのことを行います。「接続を受信する」メッセージを監督し、新しい受信機を割り当てます。
新しい「接続を受信する」メッセージを受信すると、新しい受信機が割り当てられていることが分かります。
受信機は、渋滞のgen_を任意に呼び出すことができる。tcp:acceptは自分のプロセスで許可されています。
接続を受けると、非同期メッセージを送って親プロセスに送り、直ちに業務ロジック方法を呼び出します。
ここはコードです。コメントを入れました。可読性もいいです。

-module(socket_server).
-author('Jesse E.I. Farmer <[email protected]>').
-behavior(gen_server).

-export([init/1, code_change/3, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
-export([accept_loop/1]).
-export([start/3]).

-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).

-record(server_state, {
        port,
        loop,
        ip=any,
        lsocket=null}).

start(Name, Port, Loop) ->
    State = #server_state{port = Port, loop = Loop},
    gen_server:start_link({local, Name}, ?MODULE, State, []).

init(State = #server_state{port=Port}) ->
    case gen_tcp:listen(Port, ?TCP_OPTIONS) of
        {ok, LSocket} ->
            NewState = State#server_state{lsocket = LSocket},
            {ok, accept(NewState)};
        {error, Reason} ->
            {stop, Reason}
    end.

handle_cast({accepted, _Pid}, State=#server_state{}) ->
    {noreply, accept(State)}.

accept_loop({Server, LSocket, {M, F}}) ->
    {ok, Socket} = gen_tcp:accept(LSocket),
    % Let the server spawn a new process and replace this loop
    % with the echo loop, to avoid blocking
    gen_server:cast(Server, {accepted, self()}),
    M:F(Socket).
   
% To be more robust we should be using spawn_link and trapping exits
accept(State = #server_state{lsocket=LSocket, loop = Loop}) ->
    proc_lib:spawn(?MODULE, accept_loop, [{self(), LSocket, Loop}]),
    State.

% These are just here to suppress warnings.
handle_call(_Msg, _Caller, State) -> {noreply, State}.
handle_info(_Msg, Library) -> {noreply, Library}.
terminate(_Reason, _Library) -> ok.
code_change(_OldVersion, Library, _Extra) -> {ok, Library}.
私たちはgen_を使っていますserver:castは非同期メッセージを傍受プロセスに伝達し、傍受プロセスがacceptedメッセージを受信した後、新しい受信機を割り当てます。
現在、このサーバーはあまり丈夫ではありません。何故なら、イベントの受信機が失敗したら、サーバーは新しい接続の受信を停止します。
それをOTPのようにするためには、捕獲異常により終了し、接続に失敗したときに新しい受信機を割り当てます。
汎用のechoサーバー
echoサーバは一番簡単なサーバーです。私達の新しい抽象socketサーバを使ってそれを書きます。

-module(echo_server).
-author('Jesse E.I. Farmer <[email protected]>').

-export([start/0, loop/1]).

% echo_server specific code
start() ->
    socket_server:start(?MODULE, 7000, {?MODULE, loop}).
loop(Socket) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Data} ->
            gen_tcp:send(Socket, Data),
            loop(Socket);
        {error, closed} ->
            ok
    end.
サーバーには自分の業務ロジックしか含まれていません。
コネクション処理はsocket_に実装されています。server中
ここのloop方法も最初のechoサーバーと同じです。
何かを学ぶことができますように。
エリックを理解し始めたと思います。
返信を歓迎します。特にコードの改善については、chers!