标签:
Gen_server实现了通用服务器client_server原理,几个不同的客户端去分享服务端管理的资源(如图),gen_server提供标准的接口函数和包含追踪功能以及错误报告来实现通用的服务器,同时可以作为OTP监控树的一部分。
Gen_server函数与回调函数之间的关系:
1 gen_server module Callback module 2 ----------------- --------------- 3 gen_server:start_link -----> Module:init/1 4 gen_server:call 5 gen_server:multi_call -----> Module:handle_call/3 6 gen_server:cast 7 gen_server:abcast -----> Module:handle_cast/2 8 - -----> Module:handle_info/2 9 - -----> Module:terminate/2 10 - -----> Module:code_change/3
如果回调函数失败或者是返回bad value,gen_server将终止。
Gen_server可以处理来自系统的消息,通过sys模块可以调试一个gen_server.(未实践)
注意:一个gen_server不能自动的捕获exit信号,必须在回调Module:init时设置process_flag(trap_exit,true)(实例3.2).
如果请求的gen_server不存在或参数是bad arguments,那么gen_server的所有请求都将fail.
如果回调函数中指定了hibernate,那么gen_server进程将进入hibernate,这对于一个长时间的空闲的进程非常有用,因为可以进行垃圾回收和减少内存占用。但是,要非常小心的使用hibernate,因为在hibernate到wake_up之间,至少有两个垃圾回收器,对于一个请求频繁的server不划算。
start(Module, Args, Options) -> Result
start(ServerName, Module, Args, Options) -> Result
start_link与start的区别是:1.start用于创建一个独立的gen_server,但是可以通过参数{spawn_opt,[link]}来达到start_link的效果;2.start_link用于创建一个在监控树下的gen_server
对其参数的解析:
ServerName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}
如果没有该参数,则用pid()来作为名字
Module:回调模块
Options:[{debug,Dbgs} | {timeout,Time} | {spawn_opt,SOpts}]
{timeout,Time}是初始化的的时间限制,超出时间限制返回{error,timeout}
{spawn_opt,SOpt}是在生成gen_server可以设置的参数,通过内建函数spawn_opt来实现
SOpt = Option = link %% 与创建的gen_server连接| monitor %% 在这里会报错
| {priority, Level :: priority_level()} %%设置优先级
| {fullsweep_after, Number :: integer() >= 0} %%多长时间进行一次全局扫描,进行垃圾回收
| {min_heap_size, Size :: integer() >= 0} %%最小堆内存
| {min_bin_vheap_size, VSize :: integer() >= 0} %%最小二进制虚拟堆内存
priority_level() = low | normal | high | max
如果创建gen_server成功返回{ok, Pid()},如果创建的进程已经存在返回{error,{already_started,Pid}}
如果回调函数init失败返回{error, Reason},如果回调函数返回{stop,Reason}或ignore则返回{error,Reason}或ignore
call(ServerRef, Request) -> Reply
multi_call(Name, Request) -> Result %%会调用所有节点
multi_call(Nodes, Name, Request) -> Result
multi_call(Nodes, Name, Request, Timeout) -> Result %%调用指定列表节点,并有时间限制
该函数的作用是对指定节点列表上本地注册名称是Name的gen_server发起请求,然后等待返回
其回调函数为Module:handle_call/3
Nodes = [Node]
节点列表
Timeout = int()>=0 | infinity
同步调用的时间限制,infinity表示无穷大,默认为infinity毫秒
如果在指定的时间限制内未返回,该节点为BadNodes
注意:对于非Erlang节点等待可能无穷大,例如Java或C节点。(未验证)
Result = {Replies,BadNodes}
Replies = [{Node,Reply}]
BadNodes = [Node]
没有响应的节点列表。
cast(ServerRef, Request) -> ok
ServerRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()
参数信息和gen_server:call同义
发送一个异步请求给Module:handle_cast处理,并立即返回ok.如果节点或gen_server不存在请求将被ignore.
abcast(Name, Request) -> abcast
abcast(Nodes, Name, Request) -> abcast
发送一个异步请求给指定节点并且本地注册名称为Name的gen_server,并立即返回abcast.如果节点不存在或者Name不存在,请求将被忽略掉ignore.
reply(Client, Reply) -> Result
Client - see below
该函数用于gen_server向一个指定的客户端发送信息,但是,请求函数call or multi_call的回调函数Module:handle_call是没有定义Reply.
客户端必须是回调函数提供的From,Reply是任意的数据结构作为call or multi_call的返回值。
enter_loop(Module, Options, State)
enter_loop(Module, Options, State, ServerName)
enter_loop(Module, Options, State, Timeout)
enter_loop(Module, Options, State, ServerName, Timeout)
Options = [Option]
Dbgs = [trace | log | statistics | {log_to_file,FileName} | {install,{FuncState}}]
ServerName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}
Timeout = int() | infinity
该函数的功能是让一个已经存在的进程成为gen_server进程,通过请求进程让它进入gen_server循环成为gen_server进程,该普通进程的创建必须通过proc_lib模块(实例3.4)。
这个函数更加的有用对于要进行比gen_server还要复杂的初始化时。
Module, Options and ServerName 的意义与gen_server:start_link一样.然而,如果Servername被指定这个进程被调用前一定要相应的先注册。
State and Timeout与回调函数的Module:init一样。
Failure: 如果请求进程不是一个由proc_lib创建的进程或使用了没有注册的ServerName.
2.2 回调函数
Module:init(Args) -> Result
Types:
如果初始化失败将返回{stop,Reason} | ignore
Module:handle_call(Request, From, State) -> Result
Types:
若返回{stop,Reason,Reply,NewState} | {stop,Reason,NewState} ,前者的Reply将返回给调用函数,后者没有返回,若要返回显示调用gen_server:reply/2;两者最终都将调用Module:terminate/2来终止进程。
Module:handle_cast(Request, State) -> Result
Types:
处理cast or abcast的请求。
其参数的描述信息与Module:handle_call中的一致。
Module:handle_info(Info, State) -> Result
Types:
Module:terminate(Reason, State)
Types:
Module:code_change(OldVsn, State, Extra) -> {ok, NewState} | {error, Reason}
该函数主要用于版本的热更新,后续相关专题会介绍。
一 同步请求
通过gen_server:call或gen_server:multi_call发起同步请求,然后等待返回。
1 add1(Num1, Num2)-> 2 3 io:format("~nsync start~n"), 4 Res = gen_server:call(?SERVER, {add1, Num1, Num2}),%%同步请求 5 io:format("~nsync end~n"), 6 Res. 7 handle_call({add1, Num1, Num2}, _From, State) -> %%消息的接受处理方式 8 Num = Num1 + Num2, 9 timer:sleep(3000), 10 io:format("sleep end~n"), 11 {reply, {ok, add1, Num}, State}.
二 异步请求
通过gen_server:cast或gen_server:abcast发起异步请求立即返回ok|abcast,如果节点|gen_server|Name不存在请求ignore.
1 add2(Num1, Num2) -> 2 io:format("~nasync start~n"), 3 Res = gen_server:cast(?SERVER, {add2, Num1, Num2}),%%异步请求 4 io:format("~nasync end~n"), 5 Res. 6 handle_cast({add2, Num1, Num2}, State) -> %%消息的接受处理方式: 7 Num = Num1 + Num2, 8 io:format("~n~p~n", [Num]), 9 timer:sleep(3000), 10 io:format("sleep end~n"), 11 {noreply, State}.
三 其他消息处理
异步接收发送过来的消息,并进行相应的处理。例如在回调函数中返回{ok,State,Timeout},如果超时就会发送一条timeout消息gen_server(实例3.5).
1 add3(Num1, Num2) -> 2 io:format("~nsend start~n"), 3 Res = erlang:send(?SERVER, {add3, Num1, Num2}),%%进程消息发送(异步) 4 io:format("~nsend end~n"), 5 Res. 6 handle_info({add3, Num1, Num2}, State) -> %%消息的接受处理方式 7 Num = Num1 + Num2, 8 io:format("~n~p~n", [Num]), 9 timer:sleep(3000), 10 io:format("sleep end~n"), 11 {noreply, State}.
通过在gen_server初始化时设置process_flag(trap_exit,true)可以捕获本进程的退出消息。注意:要捕获exit就要进程之间要建立连接。
1 exit(Msg) -> 2 link(whereis(?MODULE)), 3 erlang:exit(Msg). 4 5 handle_info({‘EXIT‘, From, Reson}, State) -> 6 io:format("~p~p~n",[From, Reson]), 7 {noreply, State}; 8 handle_info(_Info, State) -> 9 {noreply, State}.
调用结果:
当Modile:handle_call没有返回时{noreply,NewState},可以通过gen_server:reply(Client, Reply)来返回给gen_server:call调用端。
1 add10(Num1, Num2)-> 2 io:format("~nsync start~n"), 3 Res = gen_server:call(?SERVER, {noreply, Num1, Num2}), 4 io:format("~nsync end~n"), 5 Res. 6 handle_call({noreply, Num1, Num2}, _From, State) -> 7 Num = Num1 + Num2, 8 timer:sleep(3000), 9 io:format("sleep end~n"), 10 gen_server:reply(_From, {ok, gen_server_reply,Num}), %%给gen_server返回Replay 11 {noreply, State}; %%无返回
gen_server:enter_loop方法可以让一个存在的普通进程成为一个gen_server进程。
1 -module(enter). 2 -author("EricLw"). 3 4 5 %% API 6 -export([start_link/0, init/1]). 7 %% API 8 -export([ add1/2]). 9 10 %% gen_server callbacks 11 -export([ 12 handle_call/3, 13 handle_cast/2, 14 handle_info/2, 15 terminate/2, 16 code_change/3]). 17 18 -define(SERVER, ?MODULE). 19 20 -record(state, {}). 21 22 %%%=================================================================== 23 %%% API 24 %%%=================================================================== 25 26 start_link() -> 27 proc_lib:start_link(?SERVER, init, [self()]). %%通过proc_link来穿件普通进程 28 29 add1(Num1, Num2)-> 30 io:format("~nsync start~n"), 31 Res = gen_server:call(?SERVER, {add1, Num1, Num2}), 32 io:format("~nsync end~n"), 33 Res. 34 35 init(Person) -> 36 proc_lib:init_ack(Person, {ok, self()}), 37 register(?MODULE, self()), 38 gen_server:enter_loop(?MODULE, [], #state{},{local,?MODULE}). %%指定了ServerName必须先注册Name 39 40 handle_call({add1, Num1, Num2}, _From, State) -> 41 Num = Num1 + Num2, 42 timer:sleep(3000), 43 io:format("sleep end~n"), 44 {reply, {ok, add1, Num}, State}; 45 handle_call(_Request, _From, State) -> 46 {reply, ok, State}. 47 48 49 handle_cast(_Request, State) -> 50 {noreply, State}. 51 52 53 handle_info(_Info, State) -> 54 {noreply, State}. 55 56 terminate(_Reason, _State) -> 57 timer:sleep(3000), 58 io:format("~nclean up~n"), 59 ok. 60 61 code_change(_OldVsn, State, _Extra) -> 62 {ok, State}.
注意:如果Servername被指定这个进程被调用前一定要相应的先注册。
在gen_server的回调函数中如init,handle_call,handle_cast,handle_info中返回了Timeout | hibernate是这是就会进入相应的处理过程。若返回了Timeout,表示在指定的时间范围内没有接收到请求或消息就会就会发出一条timeout消息,然后进行后续处理;若返回hibernate则表示进程就如hibernate状态,便于GC,当向该进程发送消息或请求的时候,唤醒该进程然后进行后续处理。
一 Timeout
1 -module(add). 2 -behaviour(gen_server). 3 %% API 4 -export([start_link/0, add1/2]). 5 %% gen_server callbacks 6 -export([init/1, 7 handle_call/3, 8 handle_cast/2, 9 handle_info/2, 10 terminate/2, 11 code_change/3]). 12 -define(SERVER, ?MODULE). 13 -record(state, {}). 14 15 start_link() -> 16 gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 17 add1(Num1, Num2)-> 18 io:format("~nsync start~n"), 19 Res = gen_server:call(?SERVER, {add1, Num1, Num2}), 20 io:format("~nsync end~n"), 21 Res. 22 %%%=================================================================== 23 %%% gen_server callbacks 24 %%%=================================================================== 25 init([]) -> 26 %%erlang:send_after(2000,?SERVER,{add3, 1, 1}), 27 %%{ok, #state{}, hibernate}. 28 {ok, #state{},3000}. 29 handle_call({add1, Num1, Num2}, _From, State) -> 30 Num = Num1 + Num2, 31 timer:sleep(3000), 32 %%io:format("sleep end~n"), 33 {reply, {ok, add1, Num}, State}; 34 handle_call(_Request, _From, State) -> 35 {reply, ok, State}. 36 37 handle_cast(_Request, State) -> 38 {noreply, State}. 39 40 handle_info(timeout, State) -> 41 io:format("~ntimeout_ericlw~n"), 42 {noreply, State}; 43 handle_info({add3, Num1, Num2}, State) -> 44 Num = Num1 / Num2, 45 io:format("~n~p~n", [Num]), 46 timer:sleep(3000), 47 io:format("sleep end~n"), 48 {noreply, State}; 49 handle_info(_Info, State) -> 50 {noreply, State}. 51 52 terminate(_Reason, _State) -> 53 timer:sleep(3000), 54 io:format("~nclean up~n"), 55 ok. 56 57 code_change(_OldVsn, State, _Extra) -> 58 {ok, State}.
打印的过时信息:
若在时限内接受到消息:
二 hibernate
在返回中用hibernate代替Timeout
后面会用专题来分析该特性,它是重要的优化手段。详细信息:erlang:hibernate和proc_lib:hibernate.
Gen_server行为作为通用服务器,良好的将业务部分与通用部分进行了分离,只要专注与业务部分也能够构建良好的系统。我们只需关心导出函数与回调函数部分,明确调用函数与回调函数的意义与联系。gen_server一般是业务模块的核心处理进程,对于请求与消息的处理,应该根据业务来定。对于需要进行非常复杂化的初始化过程,可以通过enter_loop讲一个已经初始化好的进程变成gen_server进程,对于不经常访问的进程记得返回hibernate来进行及时的GC.总之,gen_server为我们构建服务器提供了极大的便利。
标签:
原文地址:http://www.cnblogs.com/liuweiccy/p/4641702.html