以下代码来自“ Programming 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?
本示例介绍了Erlang中使用的行为概念。它说明了如何分两部分构建服务器:
第一部分是模块server1,其中仅包含可被任何服务器使用的通用功能。它的作用是维护一些可用的信息(状态变量),并准备好响应某些请求。这是gen_server行为的功能,具有更多功能。
第二部分是模块name_server。这描述了特定服务器的作用。它为服务器的用户和内部功能(回调)实现了接口,这些接口描述了针对每个特定用户请求的操作。
让我们遵循3个shell命令(请参见最后的图):
server1:start(名称服务器,名称服务器)。用户调用通用服务器的启动例程,给出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,“在家”)。用户在服务器中添加信息,调用name_server的add函数。此接口在此处隐藏了调用服务器的机制,它在客户端进程中运行。
1 / add函数使用两个参数调用服务器的rpc例程rpc(name_server, {add, Name, Place})
:回调模块和请求本身{add, Name, Place}
。rpc例程仍在客户端进程中执行,
2 /它为服务器构建一条消息,其中包含2条信息:客户端进程的pid(在此为外壳)和请求本身,然后将其发送到指定的服务器: Name ! {self(), Request},
3 /客户端等待响应。记住,我们让服务器在循环例程中等待消息。
4 /发送的消息与{From, Request}
服务器的预期格式匹配,因此服务器进入消息处理。首先,它使用两个参数回调name_server模块:request和current state Mod:handle(Request, State)
。目的是拥有通用服务器代码,因此它不知道如何处理请求。在name_server:handle / 2函数中,完成了正确的操作。通过模式匹配,该子句handle({add, Name, Place}, Dict) -> {ok, dict:store(Name, Place, Dict)};
被调用,并且创建了一个新的字典,用于存储键/值对的Name / Place(此处为joe /“在家”)。新的字典将以元组{ok,NewDict}中的响应返回。
5 /现在,通用服务器可以构建答案并将其返回给客户端From ! {Name, Response},
,并以新状态重新进入循环,loop(Name, Mod, State1)
并等待下一个请求。
6 /等待接收块的客户端收到消息{Name,Response},然后可以提取Response并将其返回给Shell,在这里就可以了。
name_server:find(joe)。用户希望从服务器获取信息。该过程与以前完全相同,这是通用服务器的兴趣所在。无论请求是什么,它都执行相同的工作。当您研究gen_server的行为时,您会看到有几种对服务器的访问,例如调用,强制转换,信息...因此,如果我们看一下此请求的流程:
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},然后可以提取Response并将其返回给Shell,现在它是joe所在的位置:“在家”。
下图显示了不同的消息交换:
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句