了解Erlang中通用服务器实现中消息的工作流程

时间:2013-11-03 19:25:01

标签: concurrency erlang actor

以下代码来自“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?

1 个答案:

答案 0 :(得分:2)

此示例介绍了Erlang中使用的行为概念。它说明了如何分两部分构建服务器:

第一部分是模块server1,它只包含任何服务器都可以使用的通用功能。它的作用是维持一些可用的 信息(状态变量)并准备好回答一些请求。这就是gen_server行为所具有的功能,具有更多功能。

第二部分是模块name_server。这个描述了特定服务器的作用。它为服务器用户和内部函数(回调)实现接口,描述了为每个特定用户请求做什么。

让我们按照3 shell命令(参见最后的图表):

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 /返回shell。

此时,与shell并行,有一个名为name_server的新生命进程在运行并等待请求。请注意,通常此步骤不是由用户完成的,而是由应用程序完成的。这就是为什么在回调模块中没有接口可以做到这一点,并且在通用服务器中直接调用start函数。

name_server:add(joe,“at home”)。用户在服务器中添加信息,调用name_server的add函数。此接口用于隐藏调用服务器的机制,它在客户端进程中运行。

1 / add函数使用2个参数rpc(name_server, {add, Name, Place})调用服务器的rpc例程:回调模块和请求本身{add, Name, Place}。 rpc例程仍然在客户端进程中执行,

2 /它为服务器构建一条由2个信息组成的消息:客户端进程的pid(此处为shell)和请求本身然后将​​其发送到指定的服务器: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)};并创建了一个新的字典,存储了键/值对Name / Place(这里是joe /“home”)。返回新的dict,并在元组{ok,NewDict}中返回响应。

5 /现在,通用服务器可以构建答案并将其返回给客户端From ! {Name, Response},,并使用新状态loop(Name, Mod, State1)重新进入循环,并等待下一个请求。

6 /正在等待接收块的客户端获取消息{Name,Response},然后可以提取响应并将其返回到shell,这里就可以了。

name_server:find(joe)。用户希望从服务器获取信息。该过程与以前完全相同,这是通用服务器的兴趣所在。无论请求是什么,它都做同样的工作。当您查看gen_server行为时,您将看到对服务器有多种访问,例如call,cast,info ......所以,如果我们查看此请求的流程:

1 /使用回调模块调用rpc并请求rpc(name_server, {find, Name}).

2 /使用客户端pid和请求向服务器发送消息

3 /等待答案

4 /服务器接收消息并使用请求Mod:handle(Request, State),回调name_server,它从句柄handle({find, Name}, Dict) -> {dict:find(Name, Dict), Dict}.获得响应,该句柄返回字典搜索的结果和字典本身。

5 /服务器构建答案并将其发送到客户端From ! {Name, Response},并重新进入具有相同状态的循环,等待下一个请求。

6 /正在等待接收块的客户端获取消息{Name,Response}然后可以提取响应并将其返回到shell,现在它就是joe所在的位置:“at home”。< / p>

下一张图片显示了不同的消息交换:

kind of sequence diagram of the 3 steps described previously