我的情况如下 - 我有一个带有函数foo()的客户端C,它执行一些计算。
我想要一个不知道foo()的服务器S来代替执行这个函数,并将结果发送回客户端。
我正在尝试确定在Erlang中执行此操作的最佳方法。我在考虑:
我还没有考虑其他方法(或语言的特征)吗?
感谢您的帮助!
答案 0 :(得分:3)
如果计算函数是自包含的,即不依赖于客户端C上的任何其他模块或函数,那么您需要做的是 fun
(功能对象)。可以通过网络发送 fun
并通过远程计算机应用 fun
,发件人已嵌入其地址和得到答案的方式。所以执行者可能只看到 fun
他们可能会或可能不会给出一个参数,但在有趣的情况下,发送者强制使用一种方法,其中答案将自动返回。 fun
是一件事中很多任务的抽象,它可以作为参数移动。
在客户端,您可以使用以下代码:
%% somewhere in the client %% client runs on node() == 'client@domain.com' -module(client). -compile(export_all). -define(SERVER,{server,'server@domain.com'}). give_a_server_a_job(Number)-> ?SERVER ! {build_fun(),Number}. build_fun()-> FunObject = fun(Param)-> Answer = Param * 20/1000, %% computation here rpc:call('client@domain.com',client,answer_ready,[Answer]) end, FunObject. answer_ready(Answer)-> %%% use Answer for all sorts of funny things.... io:format("\n\tAnswer is here: ~p~n",[Answer]).
服务器的代码如下:
%%% somewhere on the server %%% server runs on node() == 'server@domain.com' -module(server). -compile(export_all). start()-> register(server,spawn(?MODULE,loop,[])). loop()-> receive {Fun,Arg} -> Fun(Arg), %% server executes job %% job automatically sends answer back %% to client loop(); stop -> exit(normal); _ -> loop() end.
通过这种方式,作业执行者不需要知道如何发送回复,作业本身知道它将如何发送回答但是发送作业!。我在几个项目中使用过这种在网络上发送功能对象的方法,它太酷了!!!
####编辑#####
如果您有递归问题,则使用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> dynamic_compile:load_from_string(String). {module,add} 3> add:add(2,5). 7 4>
我们在上面看到的是一段模块代码,它是从字符串动态编译和加载的。如果启用此功能的库在服务器和客户端可用,则每个实体都可以将代码作为字符串发送,并在另一个实体上动态加载和执行。使用后可以卸载此代码。让我们看一下Fibonacci函数以及如何在服务器上发送和执行它:
%% This is the normal Fibonacci code which we are to convert into a string: -module(fib). -export([fib/1]). fib(N) when N == 0 -> 0; fib(N) when (N < 3) and (N > 0) -> 1; fib(N) when N > 0 -> fib(N-1) + fib(N-2). %% In String format, this would now become this piece of code StringCode = " -module(fib).\n -export([fib/1]). \nfib(N) when N == 0 -> 0;\n fib(N) when (N < 3) and (N > 0) -> 1;\n fib(N) when N > 0 -> fib(N-1) + fib(N-2). \n". %% Then the client would send this string above to the server and the server would
%% dynamically load the code and execute it
send_fib_code(Arg)-> {ServerRegName,ServerNode} ! {string,StringCode,fib,Arg}, ok. get_answer({fib,of,This,is,That}) -> io:format("Fibonacci (from server) of ~p is: ~p~n",[This,That]). %%% At Server loop(ServerState)-> receive {string,StringCode,Fib,Arg} when Fib == fib -> try dynamic_compile:load_from_string(StringCode) of {module,AnyMod} -> Answer = AnyMod:fib(Arg), %%% send answer back to client %%% should be asynchronously %%% as the channels are different & not make %% client wait rpc:call('client@domain.com',client,get_answer,[{fib,of,Arg,is,Answer}]) catch _:_ -> error_logger:error_report(["Failed to Dynamic Compile & Load Module from client"]) end, loop(ServerState); _ -> loop(ServerState) end.
这段粗略的代码可以告诉你我想说什么。但是,您记得卸载所有不可用的动态模块。此外,你可以有一种方法,服务器尝试检查已经加载这样的模块已经加载再次加载之前。我建议你不要复制并粘贴上面的代码。看看并理解它,然后编写自己的版本,可以完成这项工作。
成功!!!
答案 1 :(得分:2)
如果您执行S ! C:foo()
,它将从模块foo/1
计算客户端函数C
,并将其结果发送到进程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可以帮助您避免大部分问题的原因。