2011-10-27 53 views
4

我的場景如下 - 我有一個客戶端C與函數foo()執行一些計算。Erlang:將客戶端進程/函數卸載到服務器?

我想讓不知道foo()的服務器S改爲執行此函數,並將結果發送回客戶端。

我想確定在Erlang中執行此操作的最佳方法。我正在考慮:

  • 熱代碼交換 - 即S中的「升級」代碼,使其具有函數foo()。執行併發送回客戶端。
  • 以節點全部正確註冊的分佈式方式,沿着S的方向行事! C:FOO() - 爲「發送」功能的目的來處理/節點S

是我沒有想到的(語言或功能)還有其他的方法呢?

感謝您的幫助!

回答

3

如果計算功能是自包含的,即不依賴於客戶端C上的任何其他模塊或功能,那麼您需要做的是一個fun(功能對象)。 A fun可以通過網絡發送並通過遠程機器應用,並且在發送方已經嵌入他們的地址和獲得答案的方式。所以執行者只能看到一個fun他們可能會或可能不會提供一個參數,但在樂趣裏面,發件人已經強制一個方法,通過答案自動發回。 fun是一件事物中很多任務的抽象,它可以作爲參數移動。
在客戶端,你可以有這樣的代碼:

 

%% somewhere in the client 
%% client runs on node() == '[email protected]' 

-module(client). 
-compile(export_all). 
-define(SERVER,{server,'[email protected]'}). 

give_a_server_a_job(Number)-> ?SERVER ! {build_fun(),Number}. 

build_fun()-> 
    FunObject = fun(Param)-> 
        Answer = Param * 20/1000, %% computation here 
        rpc:call('[email protected]',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() == '[email protected]' 

-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處理遞歸。但是,客戶端和/或服務器至少需要一個庫函數來協助遞歸操作。創建一個函數,該函數應該位於客戶端以及服務器的代碼路徑中。

另一種方法是動態地將代碼從服務器發送到客戶端,然後使用庫: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> 

我們上面看到的是一段從字符串中動態編譯和加載的模塊代碼。如果在服務器和客戶端上啓用該功能的庫可用,則每個實體都可以將代碼作爲字符串發送,並將其加載並在另一個上動態執行。該代碼可以在使用後卸載。讓我們看一下斐波那契功能以及如何在服務器發送和執行:

 
%% 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('[email protected]',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

@ Muzayya-Joshua謝謝,這個答案是非常有幫助的。注意:客戶端代碼中的rpc:call後面有一個額外的逗號。如果我想發送一個遞歸函數到服務器,以斐波那契爲例?或者,如果我沒有自包含的功能,而且我想將整個調用堆棧移到服務器呢?感謝您的幫助。 – Eitan

+2

如果你想要一個遞歸的fun(),你可能會喜歡一個Erlang y-combinator:http://bc.tech.coop/blog/070611.html – MatthewToday

+0

** @ Muzayya-Joshua **引用你的原始代碼/回答:如果客戶端和服務器從同一個目錄運行,傳遞Fun將正常工作。但是,如果客戶機和服務器不在同一個目錄下(真實情況),我在節點'server @ ...'上得到_Error進程<0.39.0>,退出值爲:{undef,[{#Fun ,[5 ]},{server,loop,0}]} _ **我的問題:服務器似乎在尋找客戶端來執行該功能。爲什麼會發生這種情況?** – Eitan

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/2rpc:call/4,5是如何實現的,這就是爲什麼有OTP從大部分陷阱中拯救你的原因。

相關問題