2013-11-03 26 views
2

以下代碼來自「Programming Erlang,2nd Edition」。這是如何在Erlang中實現通用服務器的一個例子。瞭解Erlang中通用服務器實現中消息的工作流程

-module(server1). 
-export([start/2, rpc/2]). 

start(Name, Mod) -> 
    register(Name, spawn(fun() -> loop(Name, Mod, Mod:init()) end)). 

rpc(Name, Request) -> 
    Name ! {self(), Request}, 
    receive 
     {Name, Response} -> Response 
    end. 

loop(Name, Mod, State) -> 
    receive 
    {From, Request} -> 
     {Response, State1} = Mod:handle(Request, State), 
     From ! {Name, Response}, 
     loop(Name, Mod, State1) 
    end. 

-module(name_server). 
-export([init/0, add/2, find/1, handle/2]). 
-import(server1, [rpc/2]). 

%% client routines 
add(Name, Place) -> rpc(name_server, {add, Name, Place}). 
find(Name)  -> rpc(name_server, {find, Name}). 

%% callback routines 
init() -> dict:new(). 
handle({add, Name, Place}, Dict) -> {ok, dict:store(Name, Place, Dict)}; 
handle({find, Name}, Dict)  -> {dict:find(Name, Dict), Dict}. 


server1:start(name_server, name_server). 
name_server:add(joe, "at home"). 
name_server:find(joe). 

我努力去理解這些消息的工作流程。請您在執行這些函數期間幫助我理解此服務器實現的工作流程:server1:start,name_server:add和name_server:find?

回答

2

本例是對Erlang中使用的行爲概念的介紹。它說明了如何建立一個服務器分爲兩部分:

第一部分是模塊server1,其中只包含可供任何服務器使用的通用功能。它的作用是維護可用的一些 信息(狀態變量)並準備好回答一些請求。這是gen_server行爲的功能,具有更多功能。

第二部分是模塊name_server。這個描述了一個特定的服務器的功能。它爲服務器的用戶和內部函數(callback)實現接口,這些函數描述了爲每個特定的用戶請求做什麼。

允許按照3個外殼命令(參見末圖):

server1的:開始(name_server則,name_server則)。用戶調用通用服務器的啓動例程,給出2個信息(包括保存值),他想要啓動的服務器的名稱以及包含回調的模塊的名稱。通過這個通用啓動例程

1 /調用返回name_server的init例程以獲得服務器狀態Mod:init(),您可以看到通用部分不知道它將保留哪種類型的信息;該狀態由name_server:init/0例程(第一個回調函數)創建。這是一本空字典dict:new()

2 /產生一個調用通用服務器循環的新進程,其中包含3個信息(服務器名稱,回調模塊和初始服務器狀態)spawn(fun() -> loop(Name, Mod, Mod:init())。循環本身只是開始並等待接收塊中的形式爲{,}的消息。

3 /註冊名爲name_server register(Name, spawn(fun() -> loop(Name, Mod, Mod:init()) end))的新進程。

4 /返回到shell。

此時,與shell並行,有一個名爲name_server的新生活進程正在運行並等待請求。請注意,通常這一步不是由用戶完成的,而是由應用程序完成的。這就是爲什麼在回調模塊中沒有接口來做到這一點,並且在通用服務器中直接調用啓動函數。

name_server:add(joe,「at home」)。用戶在服務器中添加一個信息,調用name_server的add函數。這個接口在這裏隱藏了調用服務器的機制,並且它在客戶機進程中運行。

1/add函數使用2個參數rpc(name_server, {add, Name, Place})調用服務器的rpc例程:回調模塊和請求本身{add, Name, Place}。rpc例程仍然在客戶端進程中執行,

2 /它爲由2個信息組成的服務器構建一條消息:客戶端進程的pid(這裏是shell)和請求本身然後將​​它發送給named服務器:Name ! {self(), Request},

3 /客戶端等待響應。請記住,我們在循環例程中讓服務器等待消息。

4 /發送的消息與服務器的預期格式{From, Request}匹配,所以服務器進入消息處理。首先回調帶有2個參數的name_server模塊:請求和當前狀態Mod:handle(Request, State)。意圖是擁有一個通用的服務器代碼,所以它不知道如何處理這些請求。在name_server:handle/2函數中,正確的操作完成。由於模式匹配,子句handle({add, Name, Place}, Dict) -> {ok, dict:store(Name, Place, Dict)};被調用並創建一個新的字典,存儲鍵/值對Name/Place(這裏是joe /「at home」)。新的字典返回元組{ok,NewDict}中的響應。

5現在通用服務器可以構建答案並將其返回給客戶端From ! {Name, Response},用新狀態loop(Name, Mod, State1)重新輸入循環並等待下一個請求。

6 /正在接收塊上等待的客戶端獲得消息{Name,Response},然後可以提取Response並將其返回給shell,在這裏它就可以。

name_server:find(joe)。用戶想要從服務器獲取信息。該過程與以前完全相同,這是通用服務器的興趣所在。無論請求是什麼,它都會做同樣的工作。當您查看gen_server行爲時,您會看到服務器有幾種訪問方式,例如call,cast,info ...因此,如果我們看一下這個請求的流程:

1/call rpc與回調模塊和請求rpc(name_server, {find, Name}).

2 /發送消息到該服務器與客戶端的PID,並請求

3 /等待答案

4 /服務器接收消息和回調與所述name_server則請求Mod:handle(Request, State),它從句柄獲得響應10,它返回字典搜索的結果和字典本身。

5 /服務器構建答案並將其發送到客戶端From ! {Name, Response},,並以相同的狀態重新進入循環,等待下一個請求。

6 /在接收塊上等待的客戶端獲得消息{Name,Response},然後可以提取響應並將其返回給shell,現在它是joe所在的位置:「在家中」。

下圖爲不同的消息交換:

kind of sequence diagram of the 3 steps described previously

+0

我感謝每一秒你花寫這個答案!非常感謝。 – Chiron