我的场景如下 - 我有一个带有函数 foo() 的客户端 C,它执行一些计算。
我想要一个不知道 foo() 的服务器 S 来执行这个函数,并将结果发送回客户端。
我正在尝试确定在 Erlang 中执行此操作的最佳方法。我正在考虑:
- 热代码交换 - 即在 S 中“升级”代码,使其具有函数 foo()。执行并发送回客户端。
- 以分布式方式,所有节点都已适当注册,按照S! C:foo() - 用于将函数“发送”到进程/节点 S
还有其他我没有想到的方法(或语言的特征)吗?
谢谢您的帮助!
我的场景如下 - 我有一个带有函数 foo() 的客户端 C,它执行一些计算。
我想要一个不知道 foo() 的服务器 S 来执行这个函数,并将结果发送回客户端。
我正在尝试确定在 Erlang 中执行此操作的最佳方法。我正在考虑:
还有其他我没有想到的方法(或语言的特征)吗?
谢谢您的帮助!
如果计算函数是自包含的,即不依赖于客户端C上的任何其他模块或函数,那么您需要做的是一个fun
(功能对象)。Afun
可以通过网络发送并由远程计算机应用,并且在fun
内部,发件人已经嵌入了他们的地址和获取答案的方式。因此,执行者可能只会看到fun
他们可能会或可能不会给出论点的一个,但在乐趣中,发送者强制执行一个方法,其中答案将自动发回。它fun
是一个事物中许多任务的抽象,它可以作为参数移动。
在客户端,你可以有这样的代码:
%% 在客户端某处 %% 客户端在 node() == 'client@domain.com' 上运行 -模块(客户端)。 -编译(export_all)。 -定义(服务器,{服务器,'server@domain.com'})。 give_a_server_a_job(Number)-> ?SERVER !{build_fun(),数字}。 build_fun()-> FunObject = 乐趣(参数)-> 答案 = 参数 * 20/1000,此处计算百分比 rpc:call('client@domain.com',client,answer_ready,[Answer]) 结尾, 有趣的对象。 answer_ready(答案)-> %%% 用Answer 来处理各种有趣的事情...... io:format("\n\tAnswer is here: ~p~n",[Answer]).
然后服务器有这样的代码:
%%% 服务器某处 %%% 服务器在 node() == 'server@domain.com' 上运行 -模块(服务器)。 -编译(export_all)。 开始()-> 注册(服务器,生成(?模块,循环,[]))。 循环()-> 收到 {Fun,Arg} -> Fun(Arg), %% 服务器执行作业 %% 作业自动发回答案 %% 给客户 环形(); 停止->退出(正常); _ -> 循环() 结尾。
这样,作业执行者不需要知道如何发回答复,作业本身就知道它将如何发回答复,但是发送了作业!. 我在几个项目中使用过这种通过网络发送功能对象的方法,太酷了!!!
#### 编辑 #####
如果你有递归问题,你可以使用funs
. 但是,您将需要在客户端和/或服务器上至少有一个库函数来协助递归操作。创建一个应该在客户端和服务器的代码路径中的函数。
另一种选择是将代码从服务器动态发送到客户端,然后使用库:Dynamic Compile erlang
从客户端在服务器上加载和执行 erlang 代码。使用动态编译,这里是一个例子:
1> String = "-module(add).\n -export([add/2]). \n add(A,B) -> A + B. \n". "-module(add).\n -export([add/2]).\n add(A,B) -> A + B.\n" 2> 动态编译:加载从字符串(字符串)。 {模块,添加} 3> 添加:添加(2,5)。 7 4>
我们在上面看到的是一段从字符串动态编译和加载的模块代码。如果启用此功能的库在 server 和 client 上可用,则每个实体都可以将代码作为字符串发送,并在另一个实体上动态加载和执行。使用后可以卸载此代码。让我们看看斐波那契函数以及它是如何在服务器上发送和执行的:
%% 这是我们要转换成字符串的普通斐波那契代码: -模块(fib)。 -出口([fib/1])。 fib(N) 当 N == 0 -> 0 时; fib(N) 当 (N < 3) 和 (N > 0) -> 1 时; 当 N > 0 -> fib(N-1) + fib(N-2) 时的 fib(N)。 %% 在字符串格式中,这将变成这段代码 StringCode = " -module(fib).\n -export([fib/1]). \nfib(N) 当 N == 0 -> 0;\n fib(N) 当 (N < 3) 和 (N > 0) -> 1;\n fib(N) 当 N > 0 -> fib(N-1) + fib(N-2)。\n"。 %% 然后客户端将上面的这个字符串发送到服务器,服务器将
%% 动态加载代码并执行它
send_fib_code(Arg)-> {ServerRegName,ServerNode} !{string,StringCode,fib,Arg}, 好的。 get_answer({fib,of,This,is,That}) -> io:format("~p 的斐波那契(来自服务器)是:~p~n",[This,That]). %%% 在服务器 循环(服务器状态)-> 收到 {string,StringCode,Fib,Arg} 当 Fib == fib -> 尝试 dynamic_compile:load_from_string(StringCode) 的 {模块,AnyMod} -> 答案 = AnyMod:fib(Arg), %%% 将答案发送回客户端 %%% 应该是异步的 %%% 因为渠道不同而不是制造 %% 客户等待 rpc:call('client@domain.com',client,get_answer,[{fib,of,Arg,is,Answer}]) 抓住 _:_ -> error_logger:error_report(["无法从客户端动态编译和加载模块"]) 结尾, 循环(服务器状态); _ -> 循环(服务器状态) 结尾。
那段粗略的代码可以告诉你我想说什么。但是,请记住卸载所有不可用的动态模块。您也可以有一种方法,服务器在再次加载之前尝试检查是否已经加载了这样的模块。我建议你不要复制粘贴上面的代码。查看并理解它,然后编写自己的版本来完成这项工作。
成功 !!!
如果你这样做S ! C:foo()
,它将foo/1
从模块计算客户端函数C
并将其结果发送到 process S
。这似乎不是你想要做的。您应该执行以下操作:
% In client
call(S, M, F, A) ->
S ! {do, {M, F, A}, self()},
receive
{ok, V} -> V
end.
% In server
loop() ->
receive
{do, {M, F, A}, C} ->
C ! {ok, apply(M, F, A)},
loop()
end.
但在实际场景中,您必须做更多工作,例如标记您的客户端消息以执行选择性接收(make_ref/0
),在服务器中捕获错误并将其发送回客户端,从客户端监视服务器以捕获服务器,添加一些超时等等. 看看是如何实现的gen_server:call/2
,rpc:call/4,5
这就是为什么有 OTP 可以让你免于大多数陷阱的原因。