现在我们将重新构建四个服务器 server1,server2,server3,server4,作为在学习gen server之前的前置小项目,他们每一个都和前面有小小的不同,server4会类似于Erlang分发套装里的gen server。
Server1:基本的服务器
-module(server1).
-author("YDQ").
%% API
-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).
-author("YDQ").
%% API
-export([init/0, add/2, find/1, handle/2]).
-import(server1, [rpc/2]).
%% 客户端方法
add(Name, Place) -> rpc(name_server, {add, Name, Place}).
find(Name) -> rpc(name_server, {find, Name}).
%%回调方法
init() ->dict:new().
handle({add, Name, Place}, Dict) -> {ok, dict:store(Name, Place, Dict)};
handle({find, Name}, Dict) -> {dict:find(Name, Dict), Dict}.
这小段代码就包涵了服务器的精华,其中的spawn函数是返回一个pid,而dict:new()是一个创建一个空字典的函数。它返回一个新的空字典。
Server2:实现事务的服务器
在server1的基础上我们给它加点东西变成server2,让它在查询产生异常错误时让客户端崩溃 。
-module(server2).
-author("YDQ").
%% API
-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, crash} -> exit(rpc);
{Name, ok, Response} -> Response
end.
loop(Name, Mod, OldState) ->
receive
{From, Request} ->
try Mod:handle(Request, OldState) of
{Response, NewState} ->
From ! {Name, ok, Response},
loop(Name, Mod, NewState)
catch
_: Why ->
log_the_error(Name, Request, Why),
From ! {Name, crash},
loop(Name, Mod, OldState)
end
end.
log_the_error(Name, Request, Why) ->
io:format("Server ~p request ~p ~n"
"caused exception ~p~n",
[Name, Request, Why]).
这段代码在服务器里实现了“事务语义”,它会在处理函数抛出异常错误时用State(状态) 的初始值继续循环。但如果处理函数成功了,它就会用处理函数提供的NewState值继续循环。 当处理函数失败时,服务器会给发送问题消息的客户端发送一个消息,让它崩溃。这个客户 端不能继续工作,因为它发送给服务器的请求导致了处理函数的崩溃,但其他想要使用服务器的 客户端不会受到影响。另外,当处理函数发生错误时,服务器的状态不会改变。
这个服务器的回调模块和server1的一致,只需要把-import声明里的server1改成server2即可。
Server 3:实现热代码交换的服务器
现在我们将添加热代码交换(hot code swapping)功能。大多数服务器都执行一个固定的程 序,如果要修改服务器的行为,就必须先停止服务器,再用修改后的代码重启它。而要修改这个 服务器的行为,不用停止它,只需要发送一个包含新代码的消息,它就会提取新代码,然后用新 代码和老的会话数据继续工作。这一过程被称为热代码交换。
-module(server3).
-author("YDQ").
%% API
-export([start/2, rpc/2, swap_code/2]).
start(Name, Mod) ->
register(Name,
spawn(fun() -> loop(Name, Mod, Mod:init()) end)).
swap_code(Name, Mod) -> rpc(Name, {swap_code, Mod}).
rpc(Name, Request) ->
Name ! {self(), Request},
receive
{Name, Response} ->Response
end.
loop(Name, Mod, OldState) ->
receive
{From, {swap_code, NewCallBackMod}} ->
From ! {Name, ack},
loop(Name, NewCallBackMod, OldState);
{From, Request} ->
{Response, NewState} = Mod:handle(Request, OldState),
From ! {Name, Response},
loop(Name, Mod, NewState)
end.
如果我们向服务器发送一个交换代码消息,它就会把回调模块改为消息里包含的新模块我们下面我们来演示这个功能。
首先我们创建一个name_server的副本name_server1
-module(name_server1).
-author("YDQ").
%% API
-export([init/0, add/2, find/1, handle/2]).
-import(server3, [rpc/2]).
%% 客户端方法
add(Name, Place) -> rpc(name_server, {add, Name, Place}).
find(Name) -> rpc(name_server, {find, Name}).
%%回调方法
init() ->dict:new().
handle({add, Name, Place}, Dict) -> {ok, dict:store(Name, Place, Dict)};
handle({find, Name}, Dict) -> {dict:find(Name, Dict), Dict}.
然后我们用回调模块name_server1启动server3
现在假设想要找出这个名称服务器能提供的所有名称。API里没有函数能做到这一点,因为 name_server模块只包含访问函数add和find。 于是我们以闪电般的速度打开文本编辑器并编写一个新的回调模块。
-module(new_name_server).
-author("YDQ").
-import(server3, [rpc/2]).
%% API
-export([all_names/0, add/2, delete/1, find/1, init/0, handle/2]).
%%接口
all_names() -> rpc(name_server, allNames).
add(Name, Place) -> rpc(name_server, {add, Name, Place}).
delete(Name) -> rpc(name_server, {delete, Name}).
find(Name) -> rpc(name_server, {find, Name}).
%%回调方法
init() ->dict:new().
handle({add, Name, Place}, Dict) -> {ok, dict:store(Name, Place, Dict)};
handle({find, Name}, Dict) -> {dict:find(Name, Dict), Dict};
handle(allNames, Dict) -> {dict:fetch_keys(Dict), Dict};
handle({delete, Name}, Dict) -> {ok, dict:erase(Name, Dict)}.
编译这个模块并告知服务器交换它的回调模块.
现在我们就可以运行服务器里的新函数了。
Server 4:事务与热代码交换
在前两个服务器里,代码升级和事务语义是分开的。现在我要把它们组合到一个服务器里。
-module(server4).
-author("YDQ").
%% API
-export([start/2, swap_code/2, rpc/2]).
start(Name, Mod) ->
register(Name, spawn(fun() -> loop(Name, Mod, Mod:init()) end)).
swap_code(Name, Mod) -> rpc(Name, {swap_code, Mod}).
rpc(Name, Request) ->
Name ! {self(), Request},
receive
{Name, crash} -> exit(rpc);
{Name, ok, Response} -> Response
end.
loop(Name, Mod, OldState) ->
receive
{From , {swap_code, NewCallbackMod}} ->
From ! {Name, ok, ack},
loop(Name, NewCallbackMod, OldState);
{From, Request} ->
try Mod:handle(Request, OldState) of
{Response, NewState} ->
From ! {Name, ok, Response},
loop(Name, Mod, NewState)
catch
_: Why ->
log_the_error(Name, Request, Why),
From ! {Name, crash},
loop(Name, Mod, OldState)
end
end.
log_the_error(Name, Request, Why) ->
io:format("Server ~p request ~p~n"
"caused exception ~p~n",
[Name, Request, Why]).
这个服务器同时提供了热代码交换和事务语义,干净利落!