OTP设计原则学习笔记(2)

2、行为模式 Gen_Server

2.1 “客户-服务”模式简介

“客户-服务”模式的特点,是一个中央服务器和任意多个客户机。“客户-服务”模式用于资源管理,由服务器管理不同客户间的资源共享。

2.2 示例

《 OTP设计原则学习笔记(1)》“概述”一节的例子,是个简单服务器。可用行为模式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)并与之连接。 

● 第1个参数 {local, ch3} 指定 the gen_server 的本地注册名 ch3。

如果省略名字,这个 gen_server 得不到注册。这时必须用它的进程标示 pid。名字也可以是{global, Name},但要改用global:register_name/2 注册。

● 第2个参数ch3,是响应模块的名字。于是,接口函数(start_link, alloc, free)与响应函数(init, handle_call, handle_cast)共处同一模块。

● 第3个参数[],就象传送给响应函数init的项(term)。但是,init不需要输入数据,将它忽略。

● 第4个参数[],是选择性参数列表。用法详见 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。

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 停止运行

2.6.1 监管树的内部

如果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.

2.6.2 独立的 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, normal, State1},参数normal标明这是正常的关机;State1是gen_server的新状态值。

2.7 处理其他消息

如果gen_server收到请求之外的其他消息,必须实现响应函数handle_info(Info, State),处理这些信息。退出的消息是其他消息的一种。如果gen_server连接着其他进程,而不是监管人,gen_server应该捕捉退出信号。

 

handle_info({'EXIT', Pid, Reason}, State) ->

    ..code to handle exits here..

    {noreply, State1}.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值