2

次のコードは、「Erlang のプログラミング、第 2 版」からのものです。これは、Erlang で汎用サーバーを実装する方法の例です。

-module(server1).
-export([start/2, rpc/2]).

start(Name, Mod) -> 
  register(Name, spawn(fun() -> loop(Name, Mod, Mod:init()) end)).

rpc(Name, Request) ->
  Name ! {self(), Request},
    receive
      {Name, Response} -> Response
    end.

loop(Name, Mod, State) ->
  receive
    {From, Request} ->
      {Response, State1} = Mod:handle(Request, State),
        From ! {Name, Response},
        loop(Name, Mod, State1)
  end.

-module(name_server).
-export([init/0, add/2, find/1, handle/2]).
-import(server1, [rpc/2]).

%% client routines
add(Name, Place) -> rpc(name_server, {add, Name, Place}).
find(Name)       -> rpc(name_server, {find, Name}).

%% callback routines
init() -> dict:new().
handle({add, Name, Place}, Dict) -> {ok, dict:store(Name, Place, Dict)};
handle({find, Name}, Dict)       -> {dict:find(Name, Dict), Dict}.


server1:start(name_server, name_server).
name_server:add(joe, "at home").
name_server:find(joe).

メッセージのワークフローを理解しようと一生懸命努力しました。関数 server1:start、name_server:add、name_server:find の実行中のこのサーバー実装のワークフローを理解するのを手伝ってくれませんか?

4

1 に答える 1

3

この例は、Erlang で使用される振る舞いの概念の紹介です。これは、サーバーを 2 つの部分で構築する方法を示しています。

最初の部分はモジュール server1 で、どのサーバーでも使用できる汎用機能のみが含まれています。その役割は、利用可能ないくつかの情報 (State 変数) を維持し、いくつかの要求に応答する準備を整えることです。これが gen_server の振る舞いであり、さらに多くの機能があります。

2 番目の部分は、モジュール name_server です。これは、特定のサーバーが何をするかを説明しています。サーバーのユーザー用のインターフェースと、特定のユーザー要求ごとに何をすべきかを記述する内部関数 (コールバック) を実装します。

3 つのシェル コマンドに従います (最後の図を参照)。

server1:start(name_server, name_server)。ユーザーは汎用サーバーの開始ルーチンを呼び出し、2 つの情報 (保存値を含む)、開始するサーバーの名前、およびコールバックを含むモジュールの名前を指定します。これにより、一般的な開始ルーチン

1/ name_server の init ルーチンをコールバックしてサーバーの状態を取得しますMod:init()。ジェネリック部分は保持する情報の種類を認識していないことがわかります。状態は、最初のコールバック関数である name_server:init/0 ルーチンによって作成されます。ここでは空の辞書dict:new()です。

2/ 3 つの情報 (サーバー名、コールバック モジュール、初期サーバー状態) を使用して、汎用サーバー ループを呼び出す新しいプロセスを生成しますspawn(fun() -> loop(Name, Mod, Mod:init())。ループ自体は単に開始し、受信ブロックで{ , } の形式のメッセージを待ちます。

3/ 新しいプロセスを name_server という名前で登録しregister(Name, spawn(fun() -> loop(Name, Mod, Mod:init()) end))ます。

4/ シェルに戻ります。

この時点で、シェルと並行して、name_server という名前の新しいプロセスが実行され、要求を待っています。通常、この手順はユーザーではなく、アプリケーションによって行われることに注意してください。これが、コールバック モジュールにそれを行うためのインターフェイスがなく、汎用サーバーで start 関数が直接呼び出される理由です。

name_server:add(joe, "at home"). ユーザーは、 name_server の add 関数を呼び出して、サーバーに情報を追加します。このインターフェイスは、サーバーを呼び出すメカニズムを隠すためにあり、クライアント プロセスで実行されます。

1/ add 関数は、サーバーの rpc ルーチンを 2 つrpc(name_server, {add, Name, Place})のパラメーター (コールバック モジュールと要求自体) で呼び出します{add, Name, Place}。rpc ルーチンは引き続きクライアント プロセスで実行されます。

2/ クライアント プロセス (ここではシェル) の pid と要求自体の 2 つの情報で構成されるサーバーへのメッセージを作成し、それを指定されたサーバーに送信します。Name ! {self(), Request},

3/ クライアントは応答を待ちます。ループ ルーチンでサーバーをメッセージ待ちのままにしたことを思い出してください。

4/ 送信されたメッセージは、サーバーの予想される形式{From, Request}と一致するため、サーバーはメッセージ処理に入ります。まず、リクエストと現在の状態の 2 つのパラメーターを使用して name_server モジュールをコールバックしますMod:handle(Request, State)。目的は汎用サーバー コードを使用することなので、要求をどう処理するかは認識されません。name_server:handle/2 関数では、正しい操作が行われます。パターン マッチングのおかげで、句handle({add, Name, Place}, Dict) -> {ok, dict:store(Name, Place, Dict)};が呼び出され、キーと値のペアの名前/場所 (ここでは joe/"at home") を格納する新しい辞書が作成されます。新しい dict はタプル {ok,NewDict} の応答と共に返されます。

5/ これで、汎用サーバーは応答を構築し、それをクライアントに返しFrom ! {Name, Response},、新しい状態でループに再び入りloop(Name, Mod, State1)、次の要求を待つことができます。

6/ 受信ブロックで待機していたクライアントは、メッセージ {Name, Response} を受け取り、応答を抽出してシェルに返すことができます。ここでは、単純に問題ありません。

name_server:find(ジョー)。ユーザーは、サーバーから情報を取得したいと考えています。プロセスは以前とまったく同じであり、汎用サーバーの関心事です。要求が何であれ、同じ仕事をします。gen_server の動作を調べてみると、call、cast、info など、サーバーへのアクセスにはいくつかの種類があることがわかります。このリクエストの流れを見ると、次のようになります。

1/ コールバック モジュールとリクエストで rpc を呼び出すrpc(name_server, {find, Name}).

2/ クライアント pid とリクエストを含むメッセージをサーバーに送信する

3/答えを待つ

4/ サーバーはメッセージを受信し、リクエストで name_server をコールバックし、辞書検索の結果と辞書自体を返すMod:handle(Request, State),ハンドルから応答を取得します。handle({find, Name}, Dict) -> {dict:find(Name, Dict), Dict}.

5/ サーバーは応答を作成してクライアントに送信From ! {Name, Response},し、同じ状態でループに再び入り、次の要求を待ちます。

6/ 受信ブロックで待機していたクライアントは、メッセージ {Name, Response} を受け取り、応答を抽出してシェルに返すことができます。これは、joe が「在宅」している場所です。

次の図は、さまざまなメッセージ交換を示しています。

前述の 3 つのステップの一種のシーケンス図

于 2013-11-04T07:38:17.200 に答える