2013-11-03 23 views
2

以下代码来自“Programming Erlang,2nd Edition”。这是如何在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_server:add和name_server:find?

回答

2

本例是对Erlang中使用的行为概念的介绍。它说明了如何建立一个服务器分为两部分:

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

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

允许按照3个外壳命令(参见末图):

server1的:开始(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的新生活进程正在运行并等待请求。请注意,通常这一步不是由用户完成的,而是由应用程序完成的。这就是为什么在回调模块中没有接口来做到这一点,并且在通用服务器中直接调用启动函数。

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)和请求本身然后将​​它发送给named服务器: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 /“at home”)。新的字典返回元组{ok,NewDict}中的响应。

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

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

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

1/call rpc与回调模块和请求rpc(name_server, {find, Name}).

2 /发送消息到该服务器与客户端的PID,并请求

3 /等待答案

4 /服务器接收消息和回调与所述name_server则请求Mod:handle(Request, State),它从句柄获得响应10,它返回字典搜索的结果和字典本身。

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

6 /在接收块上等待的客户端获得消息{Name,Response},然后可以提取响应并将其返回给shell,现在它是joe所在的位置:“在家中”。

下图为不同的消息交换:

kind of sequence diagram of the 3 steps described previously

+0

我感谢每一秒你花写这个答案!非常感谢。 – Chiron