如何实现可以成为任何一种特定服务器的通用 Erlang 服务器
How to implement general Erlang server that can become any kind of specific server
目前我正在试验 Erlang,并希望实现一种由 Joe Armstrong 描述的通用服务器(如 this one)。总体思路是创建一个通用服务器,我们稍后可以告诉它成为一个特定的服务器,如下所示:
universal_server() ->
receive
{become, F} ->
F()
end.
和一些特定的服务器:
factorial_server() ->
receive
{From, N} ->
From ! factorial(N),
factorial_server()
end.
factorial(0) -> 1;
factorial(N) -> N * factorial(N-1).
最后向通用服务器发送 "become factorial server" 消息:
test() ->
Pid = spawn(fun universal_server/0),
Pid ! {become, fun factorial_server/0},
Pid ! {self(), 50},
receive
X -> X
end.
我想做的是实现一个可以接受多个后续 "become" 消息的通用服务器(这样我就可以发送一个 "become factorial server" 消息,然后发送一个 "become other kind of specific server" 消息...).
一种天真的方法是要求每个特定的服务器实现都将在 receive
子句中包含 {become, F}
模式。也许我可以定义所有特定服务器的一般形状(包含 {become, F}
子句)并将其他消息传播到回调。
我的问题是,如何以干净、智能的方式实现这种情况?
这是我的:
-module(myserver).
-export([start/0, init/0]).
start() ->
erlang:spawn_link(?MODULE, init, []).
init() ->
State = undefined, % You may want to do something at startup
loop(State).
% if something went wrong comment above line and uncomment below line:
% exit(element(2, catch loop(State))).
loop(MyState) ->
Msg =
receive
Any ->
Any
end,
handle_message(Msg, MyState).
% We got a message for becoming something:
handle_message({become, Mod, InitArgument}, _) ->
% Also our callback may want to do something at startup:
CallbackState = Mod:init(InitArgument),
loop({Mod, CallbackState});
% We got a message and we have a callback:
handle_message(Other, {Mod, CallbackState}) ->
case Mod:handle_message(Other, CallbackState) of
stop ->
loop(undefined);
NewCallbackState ->
loop({Mod, NewCallbackState})
end;
% We got a message and we Don't have a callback:
handle_message(Other, undefined) ->
io:format("Don't have any callback for handling ~p~n", [Other]),
loop(undefined).
我还为我的服务器编写了一个简单的 counter
程序:
-module(counter).
-export([init/1, handle_message/2]).
init(Start) ->
Start.
handle_message(inc, Number) ->
Number + 1;
handle_message(dec, Number) ->
Number - 1;
handle_message({From, what_is}, Number) ->
From ! Number;
handle_message(stop, _) ->
stop;
handle_message(Other, Number) ->
io:format("counter got unknown message ~p~n", [Other]),
Number.
让我们测试一下:
Eshell V10.1 (abort with ^G)
1> S = myserver:start().
<0.79.0>
2> S ! hello.
Don't have any callback for handling hello
hello
3> S ! {become, counter, 10}.
{become,counter,10}
4> S ! hi.
counter got unknown message hi
hi
5> S ! inc.
inc
6> S ! dec.
dec
7> S ! dec.
dec
8> S ! {self(), what_is}.
{<0.77.0>,what_is}
9> flush().
Shell got 9
ok
10> S ! stop.
stop
11> S ! inc.
Don't have any callback for handling inc
inc
我们应该怎么做才能完成它?
如您所见,这不是生产就绪代码,我们应该:
- 有办法设置初始化超时。
- 有办法设置进程生成选项。
- 有办法在本地或全局注册进程或使用自定义进程注册表。
- 调用
try catch
中的回调函数。
- 确保消息回复是针对当前消息传递的,而不是针对我们进程之前发送的其他消息! (
gen
模块提供的 call
)。
- 当我们的starter进程死掉的时候杀死我们自己,如果starter链接到我们,就不要成为僵尸进程!
- 在每个回调的末尾调用一个函数,让他们清理那些东西(如果有)(您可以将其命名为
terminate
)。
- 兼容OTP
sys
模块,所以我们应该定义它的回调函数。参见 sys callback functions。然后我们可以将我们的进程切换到调试模式,查看它的 I/O,在重新加载代码时改变它的状态,等等
请注意,proc_lib
和 gen
模块可以帮助您完成大部分工作。
目前我正在试验 Erlang,并希望实现一种由 Joe Armstrong 描述的通用服务器(如 this one)。总体思路是创建一个通用服务器,我们稍后可以告诉它成为一个特定的服务器,如下所示:
universal_server() ->
receive
{become, F} ->
F()
end.
和一些特定的服务器:
factorial_server() ->
receive
{From, N} ->
From ! factorial(N),
factorial_server()
end.
factorial(0) -> 1;
factorial(N) -> N * factorial(N-1).
最后向通用服务器发送 "become factorial server" 消息:
test() ->
Pid = spawn(fun universal_server/0),
Pid ! {become, fun factorial_server/0},
Pid ! {self(), 50},
receive
X -> X
end.
我想做的是实现一个可以接受多个后续 "become" 消息的通用服务器(这样我就可以发送一个 "become factorial server" 消息,然后发送一个 "become other kind of specific server" 消息...).
一种天真的方法是要求每个特定的服务器实现都将在 receive
子句中包含 {become, F}
模式。也许我可以定义所有特定服务器的一般形状(包含 {become, F}
子句)并将其他消息传播到回调。
我的问题是,如何以干净、智能的方式实现这种情况?
这是我的:
-module(myserver).
-export([start/0, init/0]).
start() ->
erlang:spawn_link(?MODULE, init, []).
init() ->
State = undefined, % You may want to do something at startup
loop(State).
% if something went wrong comment above line and uncomment below line:
% exit(element(2, catch loop(State))).
loop(MyState) ->
Msg =
receive
Any ->
Any
end,
handle_message(Msg, MyState).
% We got a message for becoming something:
handle_message({become, Mod, InitArgument}, _) ->
% Also our callback may want to do something at startup:
CallbackState = Mod:init(InitArgument),
loop({Mod, CallbackState});
% We got a message and we have a callback:
handle_message(Other, {Mod, CallbackState}) ->
case Mod:handle_message(Other, CallbackState) of
stop ->
loop(undefined);
NewCallbackState ->
loop({Mod, NewCallbackState})
end;
% We got a message and we Don't have a callback:
handle_message(Other, undefined) ->
io:format("Don't have any callback for handling ~p~n", [Other]),
loop(undefined).
我还为我的服务器编写了一个简单的 counter
程序:
-module(counter).
-export([init/1, handle_message/2]).
init(Start) ->
Start.
handle_message(inc, Number) ->
Number + 1;
handle_message(dec, Number) ->
Number - 1;
handle_message({From, what_is}, Number) ->
From ! Number;
handle_message(stop, _) ->
stop;
handle_message(Other, Number) ->
io:format("counter got unknown message ~p~n", [Other]),
Number.
让我们测试一下:
Eshell V10.1 (abort with ^G)
1> S = myserver:start().
<0.79.0>
2> S ! hello.
Don't have any callback for handling hello
hello
3> S ! {become, counter, 10}.
{become,counter,10}
4> S ! hi.
counter got unknown message hi
hi
5> S ! inc.
inc
6> S ! dec.
dec
7> S ! dec.
dec
8> S ! {self(), what_is}.
{<0.77.0>,what_is}
9> flush().
Shell got 9
ok
10> S ! stop.
stop
11> S ! inc.
Don't have any callback for handling inc
inc
我们应该怎么做才能完成它?
如您所见,这不是生产就绪代码,我们应该:
- 有办法设置初始化超时。
- 有办法设置进程生成选项。
- 有办法在本地或全局注册进程或使用自定义进程注册表。
- 调用
try catch
中的回调函数。 - 确保消息回复是针对当前消息传递的,而不是针对我们进程之前发送的其他消息! (
gen
模块提供的call
)。 - 当我们的starter进程死掉的时候杀死我们自己,如果starter链接到我们,就不要成为僵尸进程!
- 在每个回调的末尾调用一个函数,让他们清理那些东西(如果有)(您可以将其命名为
terminate
)。 - 兼容OTP
sys
模块,所以我们应该定义它的回调函数。参见 sys callback functions。然后我们可以将我们的进程切换到调试模式,查看它的 I/O,在重新加载代码时改变它的状态,等等
请注意,proc_lib
和 gen
模块可以帮助您完成大部分工作。