Erlang: Разгрузить клиентский процесс / функцию на сервер? - PullRequest
4 голосов
/ 27 октября 2011

Мой сценарий выглядит следующим образом - У меня есть клиент C с функцией foo (), которая выполняет некоторые вычисления.

Я бы хотел, чтобы сервер S, который не знает о foo (), вместо этого выполнил эту функцию и отправил результат обратно клиенту.

Я пытаюсь определить лучший способ выполнить это в Эрланге. Я рассматриваю:

  • Горячая замена кода - то есть «обновить» код в S так, чтобы он имел функцию foo (). Выполнить и отправить обратно клиенту.
  • Распределенным образом, где все узлы соответствующим образом зарегистрированы, сделайте что-то вроде S! C: foo () - с целью «отправки» функции на процесс / узел S

Существуют ли другие методы (или особенности языка), о которых я не думаю?

Спасибо за помощь!

Ответы [ 2 ]

3 голосов
/ 28 октября 2011

Если функция вычисления является автономной, т. Е. Не зависит от каких-либо других модулей или функций на клиенте C, то вам нужно сделать следующее: fun (Функциональный Объекты). A 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.

Таким образом, исполнителю задания не нужно знать, как отправить ответ обратно, Само задание приходит, зная, как оно отправит ответ на отправленное задание! . Я использовал этот метод отправки функциональных объектов по сети в нескольких проектах, это так здорово !!!

#### EDIT #####

Если у вас есть рекурсивная проблема, вы управляете рекурсией, используя funs. Однако вам понадобится как минимум одна библиотечная функция на клиенте и / или сервере, чтобы помочь в рекурсивных манипуляциях. Создайте функцию, которая должна быть в пути кода клиента и сервера.

Другой вариант - динамически отправлять код с сервера на клиент, а затем использовать библиотеку: <a href="https://github.com/JacobVorreuter/dynamic_compile" rel="nofollow">Dynamic Compile erlang</a> для загрузки и выполнения кода 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>

То, что мы видим выше, это фрагмент кода модуля, который динамически компилируется и загружается из строки. Если библиотека, обеспечивающая это, доступна на сервере и клиенте, то каждый объект может отправлять код в виде строки, а другой загружать и выполнять его динамически. Этот код может быть выгружен после использования. Давайте посмотрим на функцию Фибоначчи и как ее можно отправить и выполнить на сервере:

<b>%% This is the normal Fibonacci code which we are to convert into a string:</b>

-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).

<b>%% In String format, this would now become this piece of code</b>
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".

<b>%% Then the client would send this string above to the server and the server would <br>%% dynamically load the code and execute it</b><br>
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]).

<b>%%% At Server</b>
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.

Этот кусок грубого кода может показать вам, что я пытаюсь сказать. Однако вы не забудьте выгружать все неиспользуемые динамические модули. Также у вас может быть способ, которым сервер пытается проверить, загружался ли такой модуль уже перед его повторной загрузкой. Я советую вам не копировать и не вставлять приведенный выше код. Посмотрите на это и поймите это, а затем напишите свою собственную версию, которая может сделать эту работу.
успеха !!!

2 голосов
/ 27 октября 2011

Если вы сделаете 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, чтобы спасти вас от большинства ошибок.

...