Erlang OTP设计原则的行为理解
2012-05-12 23:52 rhinovirus 阅读(2662) 评论(0) 编辑 收藏 举报1. 首先我们来看例子1。
-module(ch1). -export([start/0]). -export([alloc/0, free/1]). -export([init/0]).
%% 启动频道服务
start() -> spawn(ch1, init, []). %% 创建一个进程,这个进程会调用init方法,参数为[]
%% 获取一个空闲的频道 alloc() -> ch1 ! {self(), alloc}, receive {ch1, Res} -> Res end.
%% 释放一个频道 free(Ch) -> ch1 ! {free, Ch}, ok.
%% 初始化频道信息 init() -> register(ch1, self()), %% 注册名称为ch1的进程 Chs = channels(), %% 调用channels方法获取 {_Allocated = [], _Free = [1,2,3,4,5,...,100]}. _Allocated 列表表示已经占用的频道,_Free 列表表示空闲的频道 loop(Chs). loop(Chs) -> receive {From, alloc} -> %% 接受到一个获取频道消息 {Ch, Chs2} = alloc(Chs), %% 调用alloc/1方法,获取一个未被占用的频道 From ! {ch1, Ch}, %% 发送一个消息到调用者 loop(Chs2); %% 继续等待消息 {free, Ch} -> %% 接受到一个释放频道Ch的消息 Chs2 = free(Ch, Chs), %% 调用free/2,将该频道放入空闲频道列表中 loop(Chs2) %% 继续等待消息 end.
%% 初始化已占用频道列表和空闲频道列表
channels() -> {_Allocated = [], _Free = lists:seq(1,100)}.
%% 获取一个空闲频道,并放入已占用频道列表中 alloc({Allocated, [H|T] = _Free}) -> {H, {[H|Allocated], T}}.
%% 将频道Ch从已占用频道列表中移除,放入空闲列表中 free(Ch, {Alloc, Free} = Channels) -> case lists:member(Ch, Alloc) of true -> {lists:delete(Ch, Alloc), [Ch|Free]}; false -> Channels end.
上面那个例子中,主要实现的是 频道服务 功能。用户通过 alloc/0 方法获取一个空闲频道, 通过free/1 去释放一个频道。我已经添加了比较详细的注释
2. 接下来,我们再看一个例子
-module(server). -export([start/1]). -export([call/2, cast/2]). -export([init/1]). start(Mod) -> spawn(server, init, [Mod]). call(Name, Req) -> Name ! {call, self(), Req}, receive {Name, Res} -> Res end. cast(Name, Req) -> Name ! {cast, Req}, ok. init(Mod) -> register(Mod, self()), State = Mod:init(), loop(Mod, State). loop(Mod, State) -> receive {call, From, Req} -> {Res, State2} = Mod:handle_call(Req, State), From ! {Mod, Res}, loop(Mod, State2); {cast, Req} -> State2 = Mod:handle_cast(Req, State), loop(Mod, State2) end.
-module(ch2). -export([start/0]). -export([alloc/0, free/1]). -export([init/0, handle_call/2, handle_cast/2]).
%% 启动服务 start() -> server:start(ch2).
%% 功能定制 获取空闲频道 alloc() -> server:call(ch2, alloc).
%% 功能定制 释放频道 free(Ch) -> server:cast(ch2, {free, Ch}). init() -> channels().
%% 获取频道的回调函数 handle_call(alloc, Chs) -> alloc(Chs). % => {Ch,Chs2} %% 释放频道的回调函数 handle_cast({free, Ch}, Chs) -> free(Ch, Chs). % => Chs2
channels() -> {_Allocated = [], _Free = lists:seq(1,100)}. alloc({Allocated, [H|T] = _Free}) -> {H, {[H|Allocated], T}}. free(Ch, {Alloc, Free} = Channels) -> case lists:member(Ch, Alloc) of true -> {lists:delete(Ch, Alloc), [Ch|Free]}; false -> Channels end.
我们将例子1中 能够通用的 代码剥离,所谓能够通用就是,如果我们要实现另一个服务器模型,也可以使用 server 服务器来实现。
另一个好处就是,例子1 中,如果你要添加一个消息类型,则需要修改服务器loop函数,而 例子2中,只需要修改定制部分的功能,和新增回调函数就能实现,
那么这样就能保证服务器的通用,稳定。
模块 server 对应的是极大简化了的Erlang/OTP中的 gen_server 行为。
标准 Erlang/OTP 行为有:
- gen_server
- 用于实现 C/S 结构中的服务端。
- gen_fsm
- 用于实现有限状态机。
- gen_event
- 用于实现事件处理功能。
- supervisor
- 用于实现监督树中的督程。
本文基于署名-非商业性使用 3.0许可协议发布,欢迎转载,演绎,但是必须保留本文的署名rhinovirus(包含链接http://www.cnblogs.com/rhinovirus/),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系。