- 浏览: 125039 次
- 性别:
- 来自: 广州
文章分类
最新评论
-
xiaoyao3857:
...
openstack dashboard简体中文汉化 -
yuky1327:
你看看系统重启之后还正常不,我们研究的时候是win7重启之后 ...
VMWare上安装OpenStack -
dotapinkcat:
2. Network ConfigurationEdit th ...
VMWare上安装OpenStack -
daduedie:
嗯,不错~~~
VMWare上安装OpenStack
转载:http://apps.hi.baidu.com/share/detail/6732177
本文是介绍Erlang/OTP系列文章的第一篇。
场景:银行ErlyBank的服务器开始运行,银行要求它成为可伸缩的系统,以便管理重要的顾客帐户。听说了Elang的强大功能后,银行雇用我们建造这一系统。为了测验我们的能力,银行首先要求我们建个简单的服务器,处理银行帐户开立和注销、存款、取款这几项业务。他们只要软件原型,无须满足实际需求。
结果:我们将使用 gen_server,搭建简单的服务器和客户端,实现银行的要求。因为只是做原型,我们就把帐户资料保存在内存里;帐户只包括用户名一项,不含其他资料。当然,存款、取款时,有确认的手续。
注意:开篇之前,假定你有Erlang语法基本知识。否则,建议你去学学。
什么是 gen_server ?
gen_server 是OTP的一项行为机制,是实现“客户/服务”关系的程序模块。它拥有许多东西,给你自由使用,这点以后再讲。以后,讲到监测器和运行时错误报告时,也会同样用到这个模块。
gen_server 的行为动作包括:
● init/1 - 服务器的初始化;
● handle_call/3 - 处理对服务器的同步调用。调用服务器的客户端被阻塞,直到本函数返回。
● handle_cast/2 - 处理对服务器的异步调用。调用的执行过程中,客户端不被阻塞。
● handle_info/2 - 是起着“收容”作用的函数。服务器收到的信息,如果不是同步调用或异步调用,就交由这个函数处理。例如,如果你的服务器与其他进程相连接,那么,要求退出进程的信息,就是这个函数处理。
● terminate/2 - 关闭服务器时,调用这个函数,做些善后处理。
● code_change/3 - 服务器运行中更新时,调用这个函数。在后面的文章中,会涉及这个函数的大量细节,但你应该至少会按照基本要求使用它。
服务器的基本结构
开写有关 gen_server的文件时,我总是使用通常的结构。你可以在文字编辑器中粘贴这个基本结构:
%%%-------------------------------------------------------------------
%%% File : eb_server.erl
%%% Author : Mitchell Hashimoto <mitchell.hashimoto@gmail.com>
%%% Description : The ErlyBank account server.
%%%
%%% Created : 5 Sep 2008 by Mitchell Hashimoto <mitchell.hashimoto@gmail.com>
%%%-------------------------------------------------------------------
-module(eb_server).
-behaviour(gen_server).
%% API
-export([start_link/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {}).
-define(SERVER, ?MODULE).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
{ok, #state{}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
如你所见,模块名字是eb_derver,意思是“ErlyBank Server”。前面提到的服务器的响应函数,它全部都实现了,并且新增加了一个:
start_link/0。这个函数用于启动服务器:
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
它调用gen_server的函数start_link/4,启动服务器,向以宏SERVER代表的服务器注册进程。该宏的默认值是本模块名称。其他参数是gen_server模块本身,还有些选择项。因为不必另有选择,它们是空值。gen_server的更多解释,见相关手册。
初始化ErlyBank服务器
调用函数gen_server:start_link之后,它会调用实际服务器的函数init。对实际服务器的初始化状态设置,应该集中在函数init。可以任意设置状态的形式,如原子、函数等等。服务器做出响应时,会返回这些状态。因此,我们要维护一个列表,其中内容是全部帐户及其资金余额。另外,我们要以用户姓名查找这些帐户。为此,我打算使用dict模块,在内存中存储“键-值”数据对。
注意面向对象编程思想:你可以认为服务器状态是其实例化的变量。在每个gen_server的返回对象中,你会得到有效的实例变量,也可以修改它们,等等。
这是我最终的init函数:
init(_Args) ->
{ok, dict:new()}.
它的确很简单!它和ErlyBank的预期返回值之一都是 {ok, State}, 但我只返回ok和空字典来表示服务器状态。我们并不使用init的参数,它是来自start_link的空列表,因而它有个下划线前缀。
同步调用还是异步调用的问题
在我们实现服务器主体之前,我要再次重申同步调用与异步调用的区别。
对服务器的同步调用,会阻塞客户端。这就意味着,客户端向服务器发送消息时,它要一直等待答复。你若需要答复,例如询问某帐户的资金余额是多少,就要用同步调用。
对gen_server的异步调用,属非阻塞或异步的操作方法。也就是说,客户端向服务器发送消息后,不管是否收到答复,它都继续运行。
Erlang保证向进程发送的一切消息,都会收到,所以,除非你明确表示需要服务器的答复,你应该使用异步调用。换句话说,如果你只是简单地要服务器确认收到了消息,你不必发送同步调用给它,因为Erlang保证它会收到消息。(译注:作者米歇尔这里的理解可能错了,应该是不保证收到消息)
开立银行帐户
开立新帐户,是ErlyBank必须做的第一件事。快试试:如果你编写开设银行帐户的程序,那么,你调用服务器是同步还是异步?使劲想想,需要返回什么值?你若回答是同步调用,你对了。通常你要确信帐户已经开立,而非仅仅假定如此。不过,这里我的实现是要使用异步调用,因为现在不检查错误。
首先,我要写API函数,调用模块外的函数创建帐户:
%% --------------------------------------------------------------------
%% Function: create_account(Name) -> ok
%% Description: Creates a bank account for the person with name Name
%% --------------------------------------------------------------------
create_account(Name) ->
gen_server:cast(?SERVER, {create, Name}).
向服务器发送异步调用。服务器是我们在函数start_link中注册的,并以宏?SERVER代表的。发送的请求是个元组{create, Name}。由于是异步调用,会立刻给出“ok”提示,这也是被调用函数要返回的值。
现在,我们需要编写处理异步调用的函数,即处理回复gen_server的值:
handle_cast({create, Name}, State) ->
{noreply, dict:store(Name, 0, State)};
handle_cast(_Msg, State) ->
{noreply, State}.
如你所见,我们增加了函数handle_cast,获取开户请求,接着把它存入字典,数值0表示当前帐户资金余额。函数handle_cast预设返回的值是 {noreply, State} ,其中State是服务器的新状态。这次我们返回的是增加了新帐户的新字典。
还要注意,我加了一个“收容”函数handle_info。虽然这不是个好习惯,但FP编程一般都使这一招法。你可以用这个函数,不动声色地接收消息;否则,程序运行时的意外事件可能总是纠缠着你。
存款业务
我们曾向雇主银行ErlyBank承诺,要编写API处理存款进入帐户的业务,并且还要有基本的确认手续;在存款入帐前,服务器也要核验帐户是否存在。银行不愿让顾客的钱流入黑洞。再次考考自己,同步调用还是异步调用?显然是同步调用。我们必须确信存款的操作成功,并且告知顾客。
照旧,我首先写出API函数:
%% --------------------------------------------------------------------
%% Function: deposit(Name, Amount) -> {ok, Balance} | {error, Reason}
%% Description: Deposits Amount into Name's account. Returns the
%% balance if successful, otherwise returns an error and reason.
%% --------------------------------------------------------------------
deposit(Name, Amount) ->
gen_server:call(?SERVER, {deposit, Name, Amount}).
没什么新鲜的。我们向服务器发送了消息。你该熟悉这些代码,它们与异步调用的几乎一模一样。二者的区别在服务器代码中:
handle_call({deposit, Name, Amount}, _From, State) ->
case dict:find(Name, State) of
{ok, Value} ->
NewBalance = Value + Amount,
Response = {ok, NewBalance},
NewState = dict:store(Name, NewBalance, State),
{reply, Response, NewState};
error ->
{reply, {error, account_does_not_exist}, State}
end;
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
哇!不少新鲜货!函数定义看似与handle_cast相同,只是我们没用参数From。From是发送调用的进程的标识(pid),因而,必要时我们可以发送附加消息。
我们答应过ErlyBank,会核验银行帐户的存在与否,首先是我们在何处做这件事。我们试图从状态字典中,找到相关存款帐户的数值。字典的查找函数,返回{ok, Value}或者error。
如果该帐户存在,返回的变量Value就是它的存款余额,于是,我们加上新的存款数额。然后,我们把新的存款余额保存到字典中,并给变量赋值。我还把返回的值存在变量中,这个变量可看做对存款API的注释:{ok, Balance}。接着,返回元组 {reply, Reply, State},服务器把变量Reply返回发出调用的进程,并且保存服务器的新状态。
另一方面,如果该帐户不存在,我们就不改变服务器的状态,但要返回元组 {error, account_does_not_exist}。
增加了处理存款的API后,eb_server.erl 源文件更新如下:
%%%-------------------------------------------------------------------
%%% File : eb_server.erl
%%% Author : Mitchell Hashimoto <mitchell.hashimoto@gmail.com>
%%% Description : The ErlyBank account server.
%%%
%%% Created : 5 Sep 2008 by Mitchell Hashimoto <mitchell.hashimoto@gmail.com>
%%%-------------------------------------------------------------------
-module(eb_server).
-behaviour(gen_server).
%% API
-export([start_link/0,
create_account/1,
deposit/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%--------------------------------------------------------------------
%% Function: create_account(Name) -> ok
%% Description: Creates a bank account for the person with name Name
%%--------------------------------------------------------------------
create_account(Name) ->
gen_server:cast(?SERVER, {create, Name}).
%%--------------------------------------------------------------------
%% Function: deposit(Name, Amount) -> {ok, Balance} | {error, Reason}
%% Description: Deposits Amount into Name's account. Returns the
%% balance if successful, otherwise returns an error and reason.
%%--------------------------------------------------------------------
deposit(Name, Amount) ->
gen_server:call(?SERVER, {deposit, Name, Amount}).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
{ok, dict:new()}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({deposit, Name, Amount}, _From, State) ->
case dict:find(Name, State) of
{ok, Value} ->
NewBalance = Value + Amount,
Response = {ok, NewBalance},
NewState = dict:store(Name, NewBalance, State),
{reply, Response, NewState};
error ->
{reply, {error, account_does_not_exist}, State}
end;
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({create, Name}, State) ->
{noreply, dict:store(Name, 0, State)};
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
取款与帐户注销
取款与帐户注销的问题,我确实想留作练习,由读者完成该模块的API函数。你若需要dict模块的帮助,可参考它的API:
http://www.erlang.org/doc/man/dict.html。对于取款业务,请注意核验帐户是否存在,以及是否出现透支。你不必处理透支问题。
如果你完成了作业,或者放弃了(但愿没有!),你可以看以下答案。
增加了取款和帐户注销业务API后,eb_server.erl 源文件更新如下:
%%%-------------------------------------------------------------------
%%% File : eb_server.erl
%%% Author : Mitchell Hashimoto <mitchell.hashimoto@gmail.com>
%%% Description : The ErlyBank account server.
%%%
%%% Created : 5 Sep 2008 by Mitchell Hashimoto <mitchell.hashimoto@gmail.com>
%%%-------------------------------------------------------------------
-module(eb_server).
-behaviour(gen_server).
%% API
-export([start_link/0,
create_account/1,
deposit/2,
withdraw/2,
delete_account/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%--------------------------------------------------------------------
%% Function: create_account(Name) -> ok
%% Description: Creates a bank account for the person with name Name
%%--------------------------------------------------------------------
create_account(Name) ->
gen_server:cast(?SERVER, {create, Name}).
%%--------------------------------------------------------------------
%% Function: deposit(Name, Amount) -> {ok, Balance} | {error, Reason}
%% Description: Deposits Amount into Name's account. Returns the
%% balance if successful, otherwise returns an error and reason.
%%--------------------------------------------------------------------
deposit(Name, Amount) ->
gen_server:call(?SERVER, {deposit, Name, Amount}).
%%--------------------------------------------------------------------
%% Function: withdraw(Name, Amount) -> {ok, Balance} | {error, Reason}
%% Description: Withdraws Amount from Name's account.
%%--------------------------------------------------------------------
withdraw(Name, Amount) ->
gen_server:call(?SERVER, {withdraw, Name, Amount}).
%%--------------------------------------------------------------------
%% Function: delete_account(Name) -> ok
%% Description: Deletes the account with the name Name.
%%--------------------------------------------------------------------
delete_account(Name) ->
gen_server:cast(?SERVER, {destroy, Name}).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
{ok, dict:new()}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({deposit, Name, Amount}, _From, State) ->
case dict:find(Name, State) of
{ok, Value} ->
NewBalance = Value + Amount,
Response = {ok, NewBalance},
NewState = dict:store(Name, NewBalance, State),
{reply, Response, NewState};
error ->
{reply, {error, account_does_not_exist}, State}
end;
handle_call({withdraw, Name, Amount}, _From, State) ->
case dict:find(Name, State) of
{ok, Value} when Value < Amount ->
{reply, {error, not_enough_funds}, State};
{ok, Value} ->
NewBalance = Value - Amount,
NewState = dict:store(Name, NewBalance, State),
{reply, {ok, NewBalance}, NewState};
error ->
{reply, {error, account_does_not_exist}, State}
end;
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({create, Name}, State) ->
{noreply, dict:store(Name, 0, State)};
handle_cast({destroy, Name}, State) ->
{noreply, dict:erase(Name, State)};
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
结论
我以本文向你展示了gen_server的基本用法,以及如何创建“客户-服务”关系。我没涉及gen_server的每个方面,如接收消息的超时设定和服务器的终止运行,但我解释了这一行为模块的实质性内容。
如果你要学习gen_server更多的知识,如回应函数、可能的返回值等更高级的用法,可以读相关手册:
http://www.erlang.org/doc/man/gen_server.html
还有,我没有触碰并很少提到函数code_change/3,而它的功能却是引人入胜的。别泄气,本系列文章最后几篇,有专门介绍。可以看到使用这个函数,对运行中的系统进行的实时更新。
本文是介绍Erlang/OTP系列文章的第一篇。
场景:银行ErlyBank的服务器开始运行,银行要求它成为可伸缩的系统,以便管理重要的顾客帐户。听说了Elang的强大功能后,银行雇用我们建造这一系统。为了测验我们的能力,银行首先要求我们建个简单的服务器,处理银行帐户开立和注销、存款、取款这几项业务。他们只要软件原型,无须满足实际需求。
结果:我们将使用 gen_server,搭建简单的服务器和客户端,实现银行的要求。因为只是做原型,我们就把帐户资料保存在内存里;帐户只包括用户名一项,不含其他资料。当然,存款、取款时,有确认的手续。
注意:开篇之前,假定你有Erlang语法基本知识。否则,建议你去学学。
什么是 gen_server ?
gen_server 是OTP的一项行为机制,是实现“客户/服务”关系的程序模块。它拥有许多东西,给你自由使用,这点以后再讲。以后,讲到监测器和运行时错误报告时,也会同样用到这个模块。
gen_server 的行为动作包括:
● init/1 - 服务器的初始化;
● handle_call/3 - 处理对服务器的同步调用。调用服务器的客户端被阻塞,直到本函数返回。
● handle_cast/2 - 处理对服务器的异步调用。调用的执行过程中,客户端不被阻塞。
● handle_info/2 - 是起着“收容”作用的函数。服务器收到的信息,如果不是同步调用或异步调用,就交由这个函数处理。例如,如果你的服务器与其他进程相连接,那么,要求退出进程的信息,就是这个函数处理。
● terminate/2 - 关闭服务器时,调用这个函数,做些善后处理。
● code_change/3 - 服务器运行中更新时,调用这个函数。在后面的文章中,会涉及这个函数的大量细节,但你应该至少会按照基本要求使用它。
服务器的基本结构
开写有关 gen_server的文件时,我总是使用通常的结构。你可以在文字编辑器中粘贴这个基本结构:
%%%-------------------------------------------------------------------
%%% File : eb_server.erl
%%% Author : Mitchell Hashimoto <mitchell.hashimoto@gmail.com>
%%% Description : The ErlyBank account server.
%%%
%%% Created : 5 Sep 2008 by Mitchell Hashimoto <mitchell.hashimoto@gmail.com>
%%%-------------------------------------------------------------------
-module(eb_server).
-behaviour(gen_server).
%% API
-export([start_link/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {}).
-define(SERVER, ?MODULE).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
{ok, #state{}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
如你所见,模块名字是eb_derver,意思是“ErlyBank Server”。前面提到的服务器的响应函数,它全部都实现了,并且新增加了一个:
start_link/0。这个函数用于启动服务器:
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
它调用gen_server的函数start_link/4,启动服务器,向以宏SERVER代表的服务器注册进程。该宏的默认值是本模块名称。其他参数是gen_server模块本身,还有些选择项。因为不必另有选择,它们是空值。gen_server的更多解释,见相关手册。
初始化ErlyBank服务器
调用函数gen_server:start_link之后,它会调用实际服务器的函数init。对实际服务器的初始化状态设置,应该集中在函数init。可以任意设置状态的形式,如原子、函数等等。服务器做出响应时,会返回这些状态。因此,我们要维护一个列表,其中内容是全部帐户及其资金余额。另外,我们要以用户姓名查找这些帐户。为此,我打算使用dict模块,在内存中存储“键-值”数据对。
注意面向对象编程思想:你可以认为服务器状态是其实例化的变量。在每个gen_server的返回对象中,你会得到有效的实例变量,也可以修改它们,等等。
这是我最终的init函数:
init(_Args) ->
{ok, dict:new()}.
它的确很简单!它和ErlyBank的预期返回值之一都是 {ok, State}, 但我只返回ok和空字典来表示服务器状态。我们并不使用init的参数,它是来自start_link的空列表,因而它有个下划线前缀。
同步调用还是异步调用的问题
在我们实现服务器主体之前,我要再次重申同步调用与异步调用的区别。
对服务器的同步调用,会阻塞客户端。这就意味着,客户端向服务器发送消息时,它要一直等待答复。你若需要答复,例如询问某帐户的资金余额是多少,就要用同步调用。
对gen_server的异步调用,属非阻塞或异步的操作方法。也就是说,客户端向服务器发送消息后,不管是否收到答复,它都继续运行。
Erlang保证向进程发送的一切消息,都会收到,所以,除非你明确表示需要服务器的答复,你应该使用异步调用。换句话说,如果你只是简单地要服务器确认收到了消息,你不必发送同步调用给它,因为Erlang保证它会收到消息。(译注:作者米歇尔这里的理解可能错了,应该是不保证收到消息)
开立银行帐户
开立新帐户,是ErlyBank必须做的第一件事。快试试:如果你编写开设银行帐户的程序,那么,你调用服务器是同步还是异步?使劲想想,需要返回什么值?你若回答是同步调用,你对了。通常你要确信帐户已经开立,而非仅仅假定如此。不过,这里我的实现是要使用异步调用,因为现在不检查错误。
首先,我要写API函数,调用模块外的函数创建帐户:
%% --------------------------------------------------------------------
%% Function: create_account(Name) -> ok
%% Description: Creates a bank account for the person with name Name
%% --------------------------------------------------------------------
create_account(Name) ->
gen_server:cast(?SERVER, {create, Name}).
向服务器发送异步调用。服务器是我们在函数start_link中注册的,并以宏?SERVER代表的。发送的请求是个元组{create, Name}。由于是异步调用,会立刻给出“ok”提示,这也是被调用函数要返回的值。
现在,我们需要编写处理异步调用的函数,即处理回复gen_server的值:
handle_cast({create, Name}, State) ->
{noreply, dict:store(Name, 0, State)};
handle_cast(_Msg, State) ->
{noreply, State}.
如你所见,我们增加了函数handle_cast,获取开户请求,接着把它存入字典,数值0表示当前帐户资金余额。函数handle_cast预设返回的值是 {noreply, State} ,其中State是服务器的新状态。这次我们返回的是增加了新帐户的新字典。
还要注意,我加了一个“收容”函数handle_info。虽然这不是个好习惯,但FP编程一般都使这一招法。你可以用这个函数,不动声色地接收消息;否则,程序运行时的意外事件可能总是纠缠着你。
存款业务
我们曾向雇主银行ErlyBank承诺,要编写API处理存款进入帐户的业务,并且还要有基本的确认手续;在存款入帐前,服务器也要核验帐户是否存在。银行不愿让顾客的钱流入黑洞。再次考考自己,同步调用还是异步调用?显然是同步调用。我们必须确信存款的操作成功,并且告知顾客。
照旧,我首先写出API函数:
%% --------------------------------------------------------------------
%% Function: deposit(Name, Amount) -> {ok, Balance} | {error, Reason}
%% Description: Deposits Amount into Name's account. Returns the
%% balance if successful, otherwise returns an error and reason.
%% --------------------------------------------------------------------
deposit(Name, Amount) ->
gen_server:call(?SERVER, {deposit, Name, Amount}).
没什么新鲜的。我们向服务器发送了消息。你该熟悉这些代码,它们与异步调用的几乎一模一样。二者的区别在服务器代码中:
handle_call({deposit, Name, Amount}, _From, State) ->
case dict:find(Name, State) of
{ok, Value} ->
NewBalance = Value + Amount,
Response = {ok, NewBalance},
NewState = dict:store(Name, NewBalance, State),
{reply, Response, NewState};
error ->
{reply, {error, account_does_not_exist}, State}
end;
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
哇!不少新鲜货!函数定义看似与handle_cast相同,只是我们没用参数From。From是发送调用的进程的标识(pid),因而,必要时我们可以发送附加消息。
我们答应过ErlyBank,会核验银行帐户的存在与否,首先是我们在何处做这件事。我们试图从状态字典中,找到相关存款帐户的数值。字典的查找函数,返回{ok, Value}或者error。
如果该帐户存在,返回的变量Value就是它的存款余额,于是,我们加上新的存款数额。然后,我们把新的存款余额保存到字典中,并给变量赋值。我还把返回的值存在变量中,这个变量可看做对存款API的注释:{ok, Balance}。接着,返回元组 {reply, Reply, State},服务器把变量Reply返回发出调用的进程,并且保存服务器的新状态。
另一方面,如果该帐户不存在,我们就不改变服务器的状态,但要返回元组 {error, account_does_not_exist}。
增加了处理存款的API后,eb_server.erl 源文件更新如下:
%%%-------------------------------------------------------------------
%%% File : eb_server.erl
%%% Author : Mitchell Hashimoto <mitchell.hashimoto@gmail.com>
%%% Description : The ErlyBank account server.
%%%
%%% Created : 5 Sep 2008 by Mitchell Hashimoto <mitchell.hashimoto@gmail.com>
%%%-------------------------------------------------------------------
-module(eb_server).
-behaviour(gen_server).
%% API
-export([start_link/0,
create_account/1,
deposit/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%--------------------------------------------------------------------
%% Function: create_account(Name) -> ok
%% Description: Creates a bank account for the person with name Name
%%--------------------------------------------------------------------
create_account(Name) ->
gen_server:cast(?SERVER, {create, Name}).
%%--------------------------------------------------------------------
%% Function: deposit(Name, Amount) -> {ok, Balance} | {error, Reason}
%% Description: Deposits Amount into Name's account. Returns the
%% balance if successful, otherwise returns an error and reason.
%%--------------------------------------------------------------------
deposit(Name, Amount) ->
gen_server:call(?SERVER, {deposit, Name, Amount}).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
{ok, dict:new()}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({deposit, Name, Amount}, _From, State) ->
case dict:find(Name, State) of
{ok, Value} ->
NewBalance = Value + Amount,
Response = {ok, NewBalance},
NewState = dict:store(Name, NewBalance, State),
{reply, Response, NewState};
error ->
{reply, {error, account_does_not_exist}, State}
end;
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({create, Name}, State) ->
{noreply, dict:store(Name, 0, State)};
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
取款与帐户注销
取款与帐户注销的问题,我确实想留作练习,由读者完成该模块的API函数。你若需要dict模块的帮助,可参考它的API:
http://www.erlang.org/doc/man/dict.html。对于取款业务,请注意核验帐户是否存在,以及是否出现透支。你不必处理透支问题。
如果你完成了作业,或者放弃了(但愿没有!),你可以看以下答案。
增加了取款和帐户注销业务API后,eb_server.erl 源文件更新如下:
%%%-------------------------------------------------------------------
%%% File : eb_server.erl
%%% Author : Mitchell Hashimoto <mitchell.hashimoto@gmail.com>
%%% Description : The ErlyBank account server.
%%%
%%% Created : 5 Sep 2008 by Mitchell Hashimoto <mitchell.hashimoto@gmail.com>
%%%-------------------------------------------------------------------
-module(eb_server).
-behaviour(gen_server).
%% API
-export([start_link/0,
create_account/1,
deposit/2,
withdraw/2,
delete_account/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%--------------------------------------------------------------------
%% Function: create_account(Name) -> ok
%% Description: Creates a bank account for the person with name Name
%%--------------------------------------------------------------------
create_account(Name) ->
gen_server:cast(?SERVER, {create, Name}).
%%--------------------------------------------------------------------
%% Function: deposit(Name, Amount) -> {ok, Balance} | {error, Reason}
%% Description: Deposits Amount into Name's account. Returns the
%% balance if successful, otherwise returns an error and reason.
%%--------------------------------------------------------------------
deposit(Name, Amount) ->
gen_server:call(?SERVER, {deposit, Name, Amount}).
%%--------------------------------------------------------------------
%% Function: withdraw(Name, Amount) -> {ok, Balance} | {error, Reason}
%% Description: Withdraws Amount from Name's account.
%%--------------------------------------------------------------------
withdraw(Name, Amount) ->
gen_server:call(?SERVER, {withdraw, Name, Amount}).
%%--------------------------------------------------------------------
%% Function: delete_account(Name) -> ok
%% Description: Deletes the account with the name Name.
%%--------------------------------------------------------------------
delete_account(Name) ->
gen_server:cast(?SERVER, {destroy, Name}).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
{ok, dict:new()}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({deposit, Name, Amount}, _From, State) ->
case dict:find(Name, State) of
{ok, Value} ->
NewBalance = Value + Amount,
Response = {ok, NewBalance},
NewState = dict:store(Name, NewBalance, State),
{reply, Response, NewState};
error ->
{reply, {error, account_does_not_exist}, State}
end;
handle_call({withdraw, Name, Amount}, _From, State) ->
case dict:find(Name, State) of
{ok, Value} when Value < Amount ->
{reply, {error, not_enough_funds}, State};
{ok, Value} ->
NewBalance = Value - Amount,
NewState = dict:store(Name, NewBalance, State),
{reply, {ok, NewBalance}, NewState};
error ->
{reply, {error, account_does_not_exist}, State}
end;
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({create, Name}, State) ->
{noreply, dict:store(Name, 0, State)};
handle_cast({destroy, Name}, State) ->
{noreply, dict:erase(Name, State)};
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
结论
我以本文向你展示了gen_server的基本用法,以及如何创建“客户-服务”关系。我没涉及gen_server的每个方面,如接收消息的超时设定和服务器的终止运行,但我解释了这一行为模块的实质性内容。
如果你要学习gen_server更多的知识,如回应函数、可能的返回值等更高级的用法,可以读相关手册:
http://www.erlang.org/doc/man/gen_server.html
还有,我没有触碰并很少提到函数code_change/3,而它的功能却是引人入胜的。别泄气,本系列文章最后几篇,有专门介绍。可以看到使用这个函数,对运行中的系统进行的实时更新。
相关推荐
本篇博客“gen_server tasting 之超简单名称服务(续)”主要探讨了如何使用gen_server来构建一个简单的名称服务系统,这是一个在分布式系统中常见的需求,用于存储和查找服务或资源的名称与地址的对应关系。...
此外,`gen_server`通常与`gen_event`配合使用,后者提供了事件管理功能,可以用来处理日志、监控和其他系统事件。这样的组合可以让开发者构建出健壮、可扩展的Erlang应用。 在实际开发中,`gen_server`行为被广泛...
《gen_lex_hash_pc:MySQL交叉编译的关键工具详解》 在IT行业中,数据库管理系统是核心组件之一,而MySQL作为开源关系型数据库的代表,广泛应用于各类项目中。在特定环境下,如嵌入式设备或资源有限的PC平台,我们...
标题"Gen_Signature_Android2"指的是一个特定的工具,它用于生成Android应用的签名,这通常是在发布应用到Google Play或其他第三方市场之前所必需的步骤。这个工具可能是为简化开发者的工作流程而设计的,使得他们...
- 处理连接请求,为每个新连接创建一个新的进程(通常是一个gen_server或gen_fsm行为)。 - 注册和登录逻辑,处理用户认证请求。 - 监听和转发消息,确保消息在正确用户间传递。 - 错误处理和异常恢复,确保系统的...
《Erlang gen_server在OcamlAsync中的实现探索》 Erlang的gen_server是其并发模型的核心组件,它提供了一种强大的状态管理和错误处理机制。而在OCaml语言中,尽管有着自己的并发库如Async,但直接移植或模仿Erlang...
前言 在上一篇中已经完成了服务端的集成,手机软件此时已经可以自动启动服务端,且运行无异常。 接下来我们就要实现我们... gen_java: true, } 字段定义与实现可在这里查到:/system/tools/hidl/build/hidl_interface.
标题“Gen_Signature_Android2.zip”中的"Gen_Signature"指的是生成签名的过程,而"Android2"可能表示这是针对Android平台的第二个版本的工具或方法。这个压缩包文件包含一个名为"Gen_Signature_Android2.apk"的应用...
1. **下载与安装**:获取Gen_Signature_Android.apk文件后,将其安装到Android设备或模拟器上。由于这是一款签名解析工具,可能需要绕过安全设置(如开启未知源安装)才能完成安装。 2. **运行工具**:启动应用后,...
标题 "srio_response_gen_srio_gen2_0_srio_gen_srio_reponse_SRIO_gen2_SR" 提到的是一个与SRIO(Serial RapidIO)相关的响应生成模块,它可能是一个硬件描述语言(如Verilog或VHDL)设计的源代码文件。SRIO是一种...
通用 TCP 服务器 通用 TCP 服务器( gen_tcp_server ) 是一种 Erlang 行为,提供快速简便的方法将 TCP 服务器功能添加到您的应用程序。 它被实现为管理 TCP 连接的主管,因为它是孩子。如何使用它? 运行make来构建。...
《Android应用签名详解——以Gen_Signature_Android.apk为例》 在移动应用开发领域,尤其是Android系统中,应用的签名是确保软件安全性和完整性的关键环节。本篇文章将详细探讨Android应用签名的重要性、原理以及...
在IT行业中,`gen_server` 是Erlang OTP(开放电信平台)框架中的一个核心行为模块,用于构建可靠且容错的服务。它提供了一种模式,使得开发者可以编写并发、状态管理和故障恢复的服务器进程。在"gen_server tasting...
### MKS Gen_L 主板关键知识点解析 #### 一、简介 MKS Gen_L 主板是针对原有 Ramps1.4 开源主板存在的问题而设计的一款优化产品。它结合了 Arduino 2560 和 Ramps1.4 的功能,旨在提供更为稳定且易于使用的解决方案...
gen_tags.vim, 用来轻松使用 ctags/gtags的vim和neovim的异步插件 gen_tags.vim 为方便用户使用 Vim/ NeoVim,简化了 ctags/ gtags的使用。它用于为你生成和维护多个平台支持的标签,在 Windows/Linux/macOS. 上测试...
gen_data_model库与“zookeeper”、“分布式”、“云原生”和“cloud native”这些标签紧密相关,意味着它可能被设计用于处理分布式系统中的数据模型。Zookeeper是一种分布式协调服务,常用于管理配置信息、命名服务...
这个“华硕Z87主板BIOS_updater_for_4th_Gen_Intel_Core_CPU.zip”压缩包就是用于更新该主板BIOS的工具,以确保与最新硬件和软件的兼容性,解决潜在问题,提高系统的稳定性和性能。 BIOS更新通常包含以下好处: 1. ...