6

我的场景如下 - 我有一个带有函数 foo() 的客户端 C,它执行一些计算。

我想要一个不知道 foo() 的服务器 S 来执行这个函数,并将结果发送回客户端。

我正在尝试确定在 Erlang 中执行此操作的最佳方法。我正在考虑:

  • 热代码交换 - 即在 S 中“升级”代码,使其具有函数 foo()。执行并发送回客户端。
  • 以分布式方式,所有节点都已适当注册,按照S! C:foo() - 用于将函数“发送”到进程/节点 S

还有其他我没有想到的方法(或语言的特征)吗?

谢谢您的帮助!

4

2 回答 2

3

如果计算函数是自包含的,即不依赖于客户端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(["无法从客户端动态编译和加载模块"]) 结尾, 循环(服务器状态); _ -> 循环(服务器状态) 结尾。

那段粗略的代码可以告诉你我想说什么。但是,请记住卸载所有不可用的动态模块。您也可以有一种方法,服务器在再次加载之前尝试检查是否已经加载了这样的模块。我建议你不要复制粘贴上面的代码。查看并理解它,然后编写自己的版本来完成这项工作。
成功 !!!

于 2011-10-28T09:48:43.087 回答
2

如果你这样做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/2rpc:call/4,5这就是为什么有 OTP 可以让你免于大多数陷阱的原因。

于 2011-10-27T16:29:40.957 回答