2. gen_server Behaviour
2 gen_server 行为
本节将在gen_server(3)
手册页中进行阅读stdblib
,其中详细介绍了所有接口函数和回调函数。
2.1 客户端-服务器原则
客户端 - 服务器模型的特点是具有中央服务器和任意数量的客户端。客户端 - 服务器模型用于资源管理操作,其中几个不同的客户端想要共享公共资源。服务器负责管理这个资源。
图2.1:客户端 - 服务器模型
2.2 示例
用简单的 Erlang 编写的一个简单的服务器的例子在中提供Overview
。服务器可以被重新使用gen_server
,导致这个回调模块:
-module(ch3).
-behaviour(gen_server).
-export([start_link/0]).
-export([alloc/0, free/1]).
-export([init/1, handle_call/3, handle_cast/2]).
start_link() ->
gen_server:start_link{local, ch3}, ch3, [], []).
alloc() ->
gen_server:call(ch3, alloc).
free(Ch) ->
gen_server:cast(ch3, {free, Ch}).
init(_Args) ->
{ok, channels()}.
handle_call(alloc, _From, Chs) ->
{Ch, Chs2} = alloc(Chs),
{reply, Ch, Chs2}.
handle_cast{free, Ch}, Chs) ->
Chs2 = free(Ch, Chs),
{noreply, Chs2}.
代码在下一节中解释。
2.3 启动Gen_Server
在上一节的示例中,gen_server
通过调用ch3:start_link()
以下命令启动:
start_link() ->
gen_server:start_link{local, ch3}, ch3, [], []) => {ok, Pid}
start_link
调用函数gen_server:start_link/4
。这个函数产生并链接到一个新的进程,一个 gen_server
。
- 第一个参数
{local, ch3}
指定名称。gen_server
然后在本地注册为ch3
。如果名称被省略,gen_server
则不会注册。相反,它的pid必须使用。该名称也可以作为{global, Name}
,在这种情况下,gen_server
注册使用global:register_name/2
。
- 第二个参数
ch3
是,回调模块的名称,即回调函数所在的模块。
接口功能(start_link
,alloc
,和free
),然后位于同一模块作为回调函数(init
,handle_call
,和handle_cast
)。这通常是很好的编程习惯,让代码对应于一个模块中包含的一个进程。
- 第三个参数,
[]
是一个传递给回调函数的术语init
。在这里,init
不需要任何indata并忽略这个论点。
- 第四个参数
[]
是,选项列表。请参阅gen_server(3)
手册页以了解可用选项。
如果名称注册成功,新gen_server
进程将调用回调函数ch3:init([])
。init
有望回归{ok, State}
,那里State
的内部状态在哪里gen_server
。在这种情况下,状态是可用频道。
init(_Args) ->
{ok, channels()}.
gen_server:start_link
是同步的。它gen_server
在初始化并准备好接收请求之前不会返回。
gen_server:start_link
必须使用,如果它gen_server
是监督树的一部分,即由监督者开始。还有一个功能是gen_server:start
,开始一个独立的gen_server
,即gen_server
不是监督树的一部分。
2.4 同步请求 - Call
同步请求alloc()
使用gen_server:call/2
以下方式实现:
alloc() ->
gen_server:call(ch3, alloc).
ch3
是gen_server
的名称并且必须与用于启动它的名称一致。alloc
是实际的请求。
该请求被写入一条消息并发送给gen_server
。当收到请求时,这些gen_server
调用handle_call(Request, From, State)
将返回一个元组{reply,Reply,State1}
。Reply
是要发送回客户端的回复,并且State1
是该状态的新值gen_server
。
handle_call(alloc, _From, Chs) ->
{Ch, Chs2} = alloc(Chs),
{reply, Ch, Chs2}.
在这种情况下,答复是分配的频道Ch
,新的状态是剩余可用频道的集合Chs2
。
因此调用ch3:alloc()
返回分配的频道Ch
,gen_server
然后等待新的请求,现在使用可用频道的更新列表。
2.5 异步请求 - Cast
异步请求free(Ch)
使用实现gen_server:cast/2
:
free(Ch) ->
gen_server:cast(ch3, {free, Ch}).
ch3
是的名字gen_server
。{free, Ch}
是实际的请求。
该请求被写入一条消息并发送给gen_server
。cast
,free
然后返回ok
。
当收到请求时,这些gen_server
调用handle_cast(Request, State)
将返回一个元组{noreply,State1}
。State1
是一个新的状态的价值gen_server
。
handle_cast{free, Ch}, Chs) ->
Chs2 = free(Ch, Chs),
{noreply, Chs2}.
在这种情况下,新状态是可用频道的更新列表Chs2
。在gen_server
现在准备新的请求。
2.6 停止
在监督树中
如果它gen_server是监督树的一部分,则不需要停止功能。该gen_server自动由supervisor终止。具体如何完成是由定义在supervisor中的shutdown strategy决定的。
如果在终止之前需要清理,则关闭策略必须是超时值,并且gen_server
必须设置为在功能中捕获退出信号init
。当命令关闭时,gen_server
然后调用回调函数terminate(shutdown, State)
:
init(Args) ->
...,
process_flag(trap_exit, true),
...,
{ok, State}.
...
terminate(shutdown, State) ->
..code for cleaning up here..
ok.
独立的 Gen_Servers
如果gen_server
不是监督树的一部分,停止功能可能很有用,例如:
...
export([stop/0]).
...
stop() ->
gen_server:cast(ch3, stop).
...
handle_cast(stop, State) ->
{stop, normal, State};
handle_cast{free, Ch}, State) ->
....
...
terminate(normal, State) ->
ok.
处理stop
请求的回调函数返回一个元组{stop,normal,State1}
,它normal
指定它是一个正常的终止,并且State1
是一个新的状态值gen_server
。这导致gen_server
呼叫terminate(normal, State1)
,然后它优雅地终止。
2.7 处理其他消息
如果gen_server
能够接收除请求之外的其他消息,则handle_info(Info, State)
必须实现回调函数来处理它们。如果gen_server
它链接到其他进程(比如supervisor)或者处理退出信号,这里的其他消息的将会是退出消息。
handle_info{'EXIT', Pid, Reason}, State) ->
..code to handle exits here..
{noreply, State1}.
code_change
方法也必须实施。
code_change(OldVsn, State, Extra) ->
..code to convert state (and more) during code change
{ok, NewState}.