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_linkalloc,和free),然后位于同一模块作为回调函数(inithandle_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).

ch3gen_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()返回分配的频道Chgen_server然后等待新的请求,现在使用可用频道的更新列表。

2.5 异步请求 - Cast

异步请求free(Ch)使用实现gen_server:cast/2

free(Ch) -> gen_server:cast(ch3, {free, Ch}).

ch3是的名字gen_server{free, Ch}是实际的请求。

该请求被写入一条消息并发送给gen_servercastfree然后返回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}.