2.Gen_Server行为

本章要与 gen_server(3) 结合起来阅读,它详细描述了所有的接口函数和回调函数。

客户端-服务器端原理

客户端-服务器端(C/S)模型的特点是:一个中央服务器和任意数量的客户端。C/S模型通常用于资源管理操作,其中一些不同的客户端要共享一个公共资源。服务器负责管理这些资源。

例子

概述中,已经有一个用普通Erlang方式写的简单服务器。这个服务器可以用 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}.

在下一节中会解释这段代码。

启动一个Gen_Server

在前一节中的代码,可以通过 ch3:start_link() 来启动这个gen_server:

start_link() ->
    gen_server:start_link({local, ch3}, ch3, [], []) => {ok, Pid}

start_link 调用了函数 gen_server:start_link/4 。这个函数产生了一个新进程一个gen_server并联接到其上。

  • 第一个参数 {local, ch3} 指定了名称。在这种情况下,gen_srever将在本地被注册为 ch3 。 如果忽略名称,那么这个gen_server就不会被注册了,这时就必须使用其pid。名称也可以以 {global, Name} 的形式给出,这种情况下gen_server则会使用 global:register_name/2 来进行注册。
  • 第二个参数, ch3, 则是回调模块的名字,也就是回调函数所放的那个模块。 在这里,接口函数( start_linkalloc 和 free )和回调函数(inithandle_call 和 handle_cast)。一般来说这是好的编程实践,将代表同一个进程的代码包含在同一个模块中。
  • 第三个参数,[], 这个值将被原封不动传递给回调函数 init。在这里,init无须任何输入数据将忽略这个参数。
  • 第四个参数,[],是参数的列表。具体的参数请查看 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是某棵监督树的一部分,即gen_server是由一个督程启动的,那么必须使用 gen_server:start_link 。还有另外一个函数 gen_server:start 用于启动一个独立的gen_server,即不是某棵监督树一部分的一个gen_server。

同步调用——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 然后gen_server将等待新的请求,并且现在保持了一个最新的可用频道的列表。

异步请求——Cast

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

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

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

请求被装在一个消息中发给gen_server的 cast ,这调用了 free ,然后返回了 ok 。

当gen_server收到请求之后,它会调用 handle_cast(Request, Stats) ,会返回一个元组 {noreply, State1} 。 State1 是gen_server状态的新值。

handle_cast({free, Ch}, Chs) ->
    Chs2 = free(Ch, Chs),
    {noreply, Chs2}.

在这里,新的状态便是更新过的可用频道列表 Chs2 。gen_server现在又可以接受新的请求了。

停止

在监督树中

若gen_server是某个监督树的一部分,则无需停止函数。它的督程会自动终止它。它的具体做法由督程中设置的 关闭策略 定义。

如果在终止之前需要进行一些清理工作,那么关闭策略必须是一个超时值,同时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_Server

如果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) 来优雅地终止。

处理其他消息

如果要想gen_server还能处理请求之外的消息,必须实现回调函数 handle_info(Info, State) 来处理他们。例如,如果gen_server联结到其它进程(非督程)上并捕获退出信号,那么其它的消息就有退出消息。

handle_info({‘EXIT‘, Pid, Reason}, State) ->
    ..code to handle exits here..
    {noreply, State1}.
时间: 2025-01-12 13:33:01

2.Gen_Server行为的相关文章

Erlang generic standard behaviours -- gen_server system msg

这是Erlang generic standard behaviors gen_server 分析的系列的最后一篇,主要分析gen_server module 辅助性的功能函数. 在gen_server 的MAIN loop 流程中,除了处理Parent 的'EXIT' 消息, user module 常规消息之外, 还处理了一类 system 消息. 这一类system 消息的来源是一个sys 的module,在Erlang OTP体系中,sys module 主要有两大类的作用,一个是热更,

Erlang点滴--杀死gen_server

前天同事碰到了一个问题:他为游戏写了一个模拟客户端的机器人程序,用的是gen_server行为.但是他启动这些机器人时并没有通过监控树,而是直接在Shell下启动了若干个.然后他就发现如果其中一个机器人进程挂掉的话,所有的机器人都会跟着挂掉. 当他把问题告诉我时我第一反应就是Shell挂掉了,因为所有的机器人都是在Shell下用start_link启动的,也就是说所有的机器人进程都和Shell进程建立了连接.因此如果其中一个机器人挂掉,它会向Shell发送EXIT消息导致Shell挂掉,Shel

Gen_server行为分析与实践

1.简介 Gen_server实现了通用服务器client_server原理,几个不同的客户端去分享服务端管理的资源(如图),gen_server提供标准的接口函数和包含追踪功能以及错误报告来实现通用的服务器,同时可以作为OTP监控树的一部分. Gen_server函数与回调函数之间的关系: 1 gen_server module Callback module 2 ----------------- --------------- 3 gen_server:start_link ----->

erlang中启动一个gen_server和一个纯粹进程的区别

1.在genserver的启动过程中,内部调用的是proc_lib:start_link(M,F,A,Time,SpawnOpts),当该函数被调用的时候,启动gen_server的进程必须wait,直到gen_server进程在执行init_it函数时,调用proc_lib:init_ack将gen_server的进程号返回给启动进程,至此一个gen_server进程才启动完成: 2.对于一个单纯的erlang进程来说,调用spawn函数来启动他,会立刻返回.

Erlang generic standard behaviours -- gen_server noblock call

在Erlang 系统中,经常需要gen_server 进程来处理共享性的数据,也就是总希望一个gen_server 进程来为多个普通进程提供某种通用性的服务,这也是gen_server 设计的初衷.但是,由于公平调度的原因,在Erlang体系中,每个process 能获得的资源都是同等的:同等的CPU时间片(还有默认情况下同等的初始化内存). 也就是gen_server 进程只能获得1/(N+1)的CPU时间片,为N个进程提供通用性的服务,而无法违背公平调度的原则使gen_server 进程获得

erlang使用gen_server实现质数服务器(手打代码,还debug了几个错误)

最近简单了解一下erlang语言,操练了一下书里的例子,有机会debug:下面是主要模块 prime_server,使用gen_server作为behaviour: 1 -module(prime_server). 2 -export([new_prime/1, start_link/0]). 3 4 -behaviour(gen_server). 5 -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/

gen_server的同步调用内部实现机制及From的前世今生

1.同步调用 首先客户端进程通过call向gen_server发起同步调用,客户端进程会monitor向gen_server进程,返回一个监控句柄Mref,同时向gen_server进程发送{Label,{self(),Mref},Request}后,客户端进程执行receive便进入阻塞状态,等待对应消息的到来.gen_server进程收到客户端发来的消息对其进行解析,解析出{self(),Mref}=From,即From是由客户端进程和客户端进程对gen_server的monitor句柄组成

"[Erl_Question00]:gen_server:reply/2

--- genserver:reply/2 reply(Client, Reply) –> Result      Types:     Client - see below     Reply = term()     Result = term() This function can be used by a gen server to explicitly send a reply to a client that called call/2,3 or multicall/2,3,4, w

Erlang --- gen_server

行为模式 gen_server代表的就是“行为模式”的一种,行为模式的目的在于为特定类型的进程提供一套模板. 启动服务器 用来启动服务器的有start/3,start/4,start_link/3,start_link/4这四个函数. 使用这些start函数之后,就会产生一个新的进程,也就是一个gen_server服务器.这些 start函数的正常情况下返回值是{ok,Pid},Pid就是这个新进程的进程号. 带link与不带的区别在于是否跟父进程建立链接,换种说法是,新启动的进程死掉后,会不会

Erlang OTP学习(1)gen_server

在<Programming Erlang>的OTP introduction章节中,作者通过循序渐进的方式,向我们展示了gen_server设计思路,现在做下总结: 在具体看gen_server之前,我们先看一个server通用框架:  在这个server里,你几乎看不到任何和具体功能相关的东西,它只提供了一个server所具备的基本框架,那它是如何运行的呢? 当我们调用start函数时,就启动了一个服务,如果服务器接收到一条消息,那么它将会把这条消息转交给Mod:handle_call或者M