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

Concurrency 了解Erlang中通用服务器实现中消息的工作流,concurrency,erlang,actor,Concurrency,Erlang,Actor,以下代码来自“编程Erlang,第二版”。这是一个如何在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}, receiv

以下代码来自“编程Erlang,第二版”。这是一个如何在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\u server:add和name\u server:find?

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

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

第二部分是模块名\u server。这一条描述了特定服务器的功能。它为服务器用户实现接口和内部函数(回调),这些函数描述如何处理每个特定的用户请求

让我们按照3个shell命令进行操作(请参见末尾的图表):

server1:start(name\u server,name\u server)。用户调用通用服务器的启动例程,给出2个信息(带有保存值)、他要启动的服务器的名称以及包含回调的模块的名称。有了这个通用的启动例程

1/回调name_server的init例程以获取服务器状态
Mod:init()
,您可以看到通用部分不知道它将保留哪种类型的信息;状态是由名称_server:init/0例程(第一个回调函数)创建的。这是一本空字典
dict:new()

2/生成一个调用通用服务器循环的新进程,其中包含3个信息(服务器名称、回调模块和初始服务器状态)
spawn(fun()->loop(name,Mod,Mod:init())
。循环本身刚刚启动,并在接收块中等待形式为{,}的消息

3/使用name\u server
register(name,spawn(fun()->loop(name,Mod,Mod:init())end))注册新进程。

4/返回外壳

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

名称\u服务器:添加(joe,“在家”)。用户在服务器中添加信息,调用名称\u服务器的添加功能。此界面用于隐藏调用服务器的机制,并在客户端进程中运行

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

2/它为服务器构建一条由2个信息组成的消息:客户端进程(这里是shell)的pid和请求本身,然后将其发送到命名服务器:
Name!{self(),request},

3/客户端等待响应。请记住,我们让服务器在循环例程中等待消息

4/发送的消息与服务器的预期格式
{From,Request}
匹配,因此服务器进入消息处理。首先,它使用两个参数回调名称\u服务器模块:请求和当前状态
Mod:handle(请求,状态)
。目的是要有一个通用的服务器代码,因此它不知道如何处理请求。在name_server:handle/2函数中,完成了正确的操作。由于模式匹配,子句
handle({add,name,Place},Dict)->{ok,Dict:store(name,Place,Dict)}调用;
,并创建一个新的字典来存储密钥/值对名称/位置(这里是joe/“在家”)。返回新的dict,并返回元组{ok,NewDict}中的响应

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

6/等待接收块的客户机获得消息{Name,Response},然后可以提取响应并将其返回给shell,在这里它是简单的ok

名称\u服务器:查找(joe).用户希望从服务器获取信息。过程与以前完全相同,这是通用服务器的兴趣所在。无论请求是什么,它都执行相同的任务。当您研究gen_服务器行为时,您将看到有几种访问服务器的方式,例如调用、强制转换、信息……因此如果我们查看此请求的流程:

1/使用回调模块调用rpc并请求
rpc(名称服务器,{find,name})。

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

3/等待答案

4/服务器接收消息并使用请求回调名称\u服务器
Mod:handle(请求,状态),
它从句柄
handle({find,name},Dict)->{Dict:find(name,Dict),Dict}获得响应。
返回字典搜索的结果和字典本身

5/服务器生成答案并将其从!{Name,Response},发送到客户机
,然后以相同的状态重新进入循环,等待下一个请求

6/等待接收块的客户机获得消息{Name,Response},然后可以提取响应并将其返回到shell,现在是joe“在家”的地方

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

<