erlang - 如何实现可以成为任何特定服务器的通用 Erlang 服务器
问题描述
目前我正在试验 Erlang,并希望实现一种由 Joe Armstrong 描述的通用服务器(如这个)。总的想法是创建一个通用服务器,我们以后可以告诉它成为一个特定的服务器,如下所示:
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).
最后向通用服务器发送“成为阶乘服务器”消息:
test() ->
Pid = spawn(fun universal_server/0),
Pid ! {become, fun factorial_server/0},
Pid ! {self(), 50},
receive
X -> X
end.
我想做的是实现一个可以接受多个后续“成为”消息的通用服务器(这样我就可以发送“成为阶乘服务器”消息,然后发送“成为其他类型的特定服务器”消息......) .
一种天真的方法是要求每个特定的服务器实现都将{become, F}
模式包含在一个receive
子句中。也许我可以有一个行为来定义所有特定服务器的一般形状(包含{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 回调函数。然后我们可以将我们的进程切换到调试模式,查看它的 I/O,在重新加载代码时改变它的状态,等等。
请注意,proc_lib
和gen
模块可以帮助您完成大部分工作。
推荐阅读
- tensorflow - 使用估计器从经过训练的模型加载检查点
- java - JPA 仅获取一级关联
- python - Ruby 与 Python 中的 AES 加密差异
- javascript - 谷歌地图上有超过 1 个标记时删除功能失败
- spring-boot - 如何捕获和处理 InvalidGrantException(用户被禁用)?
- maven - Parent POM:跨子项目共享通用配置文件
- javascript - 通过NodeJS脚本迭代数组中的项目
- java - 离线模式下的 Firebase OnSuccessListener
- django - 在生产中不使用 django 模板将 Django + Angular 部署到 EB
- android - 屏幕变暗事件/用户不活动