-module(server_template). -export([start/1]). -export([call/2, cast/2]). -export([init/1]). %%通用进程模式 start(Mod) -> spawn(server_template, init, [Mod]). init(Mod) -> register(Mod, self()), State = Mod:init(), loop(Mod, State). loop(Mod, State) -> receive {call, From, Req} -> {Res, State2} = Mod:handle_call(Req, State), From ! {Mod, Res}, loop(Mod, State2); {cast, Req} -> State2 = Mod:handle_cast(Req, State), loop(Mod, State2); stop -> stop end. %% 接口部分 call(Name, Req) -> Name ! {call, self(), Req}, receive {Name, Res} -> Res end. cast(Name, Req) -> Name ! {cast, Req}, ok.
-module(server_demo). -export([start/0]). -export([alloc/0, free/1]). -export([init/0, handle_call/2, handle_cast/2]). start() -> server_template:start(server_demo). alloc() -> server_template:call(server_demo, alloc). %%同步 free(Ch) -> server_template:cast(server_demo, {free, Ch}). %%异步 init() -> channels(). handle_call(alloc, Chs) -> alloc(Chs). handle_cast({free, Ch}, Chs) -> free(Ch, Chs). channels() -> {_Allocated = [], _Free = lists:seq(1,100)}. alloc({Allocated, [H|T] = _Free}) -> {H, {[H|Allocated], T}}. free(Ch, {Alloc, Free} = Channels) -> case lists:member(Ch, Alloc) of true -> {lists:delete(Ch, Alloc), [Ch|Free]}; false -> Channels end.
([email protected])1> server_demo:start().
<0.38.0>
([email protected])2> server_demo:alloc().
1
([email protected])3> server_demo:alloc().
2
([email protected])4> server_demo:alloc().
3
([email protected])5> server_demo:alloc().
4
([email protected])6> server_demo:free(1).
ok
下面是一个gen_server的代码模板,可以看到这个模板实际上是给出了我们需要完成的回调函数;与上面简陋的demo不同的是,下面的模板包含更多可以定制的内容:
%%gen_server代码模板 -module(new_file). -behaviour(gen_server). % -------------------------------------------------------------------- % Include files % -------------------------------------------------------------------- % -------------------------------------------------------------------- % External exports -export([]). % gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, {}). % -------------------------------------------------------------------- % Function: init/1 % Description: Initiates the server % Returns: {ok, State} | % {ok, State, Timeout} | % ignore | % {stop, Reason} % -------------------------------------------------------------------- init([]) -> {ok, #state{}}. % -------------------------------------------------------------------- % Function: handle_call/3 % Description: Handling call messages % Returns: {reply, Reply, State} | % {reply, Reply, State, Timeout} | % {noreply, State} | % {noreply, State, Timeout} | % {stop, Reason, Reply, State} | (terminate/2 is called) % {stop, Reason, State} (terminate/2 is called) % -------------------------------------------------------------------- handle_call(Request, From, State) -> Reply = ok, {reply, Reply, State}. % -------------------------------------------------------------------- % Function: handle_cast/2 % Description: Handling cast messages % Returns: {noreply, State} | % {noreply, State, Timeout} | % {stop, Reason, State} (terminate/2 is called) % -------------------------------------------------------------------- handle_cast(Msg, State) -> {noreply, State}. % -------------------------------------------------------------------- % Function: handle_info/2 % Description: Handling all non call/cast messages % Returns: {noreply, State} | % {noreply, State, Timeout} | % {stop, Reason, State} (terminate/2 is called) % -------------------------------------------------------------------- handle_info(Info, State) -> {noreply, State}. % -------------------------------------------------------------------- % Function: terminate/2 % Description: Shutdown the server % Returns: any (ignored by gen_server) % -------------------------------------------------------------------- terminate(Reason, State) -> ok. % -------------------------------------------------------------------- % Func: code_change/3 % Purpose: Convert process state when code is changed % Returns: {ok, NewState} % -------------------------------------------------------------------- code_change(OldVsn, State, Extra) -> {ok, State}.
进程如何启动
通常实现一个gen_server的应用,我们会暴露出来start,start_link方法,start是为了启动独立的(stand_alone)gen_server,start_link是用于在监控树中启动gen_server;start/start_link方法会①调用指定的init/1函数,返回值是{ok,State};②指定是否注册name,以及注册何种name,是{local, Name}还是{global, Name}③指定gen_server启动选项,这个启动选项是何种格式?其实就是spawn_opt,比如{spawn_opt,[{fullsweep_after,5000},{min_heap_size, 1000}]};我曾经介绍过proc_lib http://www.cnblogs.com/me-sa/archive/2011/11/22/erlang0017.html 要查看spawn_opt所有可用的配置项? 点击这里:http://www.erlang.org/doc/man/erlang.html#spawn_opt-4
gen_server:start(Mod, Args, Options)
gen_server:start(Name, Mod, Args, Options)
gen_server:start_link(Mod, Args, Options)
gen_server:start_link(Name, Mod, Args, Options)
注意上面的启动过程都是同步的,需要完成初始化之后才开始接收请求,这个[Erlang 0017]Erlang/OTP基础模块 proc_lib 我们已经讨论过是如何实现的;注意gen_server并没有自动进行trap_exit,如果需要就要在init函数中添加.同时,有些gen_server在启动的时候可能需要较长的时间,这个可以通过定制{timeout,10000}参数来实现,默认值是5000ms.
如何处理同步请求 Synchronous Requests - Call
gen_server:call(ServerRef, Request) -> Reply
gen_server:call(ServerRef, Request, Timeout) -> Reply
这里的ServerRef 可以是 Name | {Name,Node} | {global,GlobalName} | pid(),由于是阻塞调用,所以要么调用成功返回要么超时返回;我们可以针对特定的请求设定超时的值,做一个更细粒度的超时控制;
如何处理异步请求 Asynchronous Requests - Cast
cast(ServerRef, Request) -> ok
这里要注意的是异步请求会立即返回ok不管目的地节点/gen_server是否存在,看下面的例子:
([email protected])33> is_process_alive(pid(0,222,0)).
false
([email protected])34> gen_server:cast(pid(0,222,0),hello).
ok
([email protected])35> gen_server:call(pid(0,222,0),hello).
** exception exit: {noproc,{gen_server,call,[false,hello]}}
in function gen_server:call/2 (gen_server.erl, line 180)
通用消息处理 handle_info
This function is called by a gen_server when a timeout occurs or when it receives any other message than a synchronous
or asynchronous request (or a system message).
这个方法的定位是处理同步请求,异步请求之外的消息,如果一个消息我们能明确的知道是call还是cast就不要走这里;常见的是用它来接收退出消息:
handle_info({'EXIT', Pid, Reason}, State) ->
..code to handle exits here..
{noreply, State1}.
如何处理进程终止
如果gen_server在监控树中不需要stop函数,gen_server会由其supervisor根据shutdown策略自动终止掉.如果要在进程终止之前执行清理,shutdown策略必须设定一个timeout值而不是brutal_kill并且gen_server要在init设置trap_exit.当被supervisor命令shutdown的时候,gen_server会调用terminnate(shutdown,State),特别注意: 被supervisor终止掉,终止的原因是Reason=shutdown,这个我们之前也
init(Args) -> ..., process_flag(trap_exit, true), ..., {ok, State}. ... terminate(shutdown, State) -> ..code for cleaning up here.. ok.
如果gen_server不是supervisor的一部分,stop方法就很有用了:
... 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.
通过调用terminate方法,gen_server可以优雅的关闭掉了. 如果结束的消息不是normal,shutdowngen_server就会被认为是异常终止并通过error_logger:format/2产生错误报告.
Note: if any reason other than
normal
,shutdown
or{shutdown, Term}
is used whenterminate/2
is called, the OTP framework will see this as a failure and start logging a bunch of stuff here and there for you.
好了,就到这里,休息一下
劳逸结合每次推荐一张唱片,今天推荐《台湾百佳唱片》第一张,1982年滚石唱片出品 罗大佑《之乎者也》
1.鹿港小镇 2.恋曲1980 3.童年4.错误 5.摇篮曲 6.之乎者也 7.乡愁四韵 8.将进酒 9.光阴的故事 10.蒲公英
遥远的路程昨日的梦以及远去的笑声
再次的见面我们又历经了多少的路程
熟悉的旧日熟悉的你有着旧日狂热的梦
不再是旧日熟悉的我有着依然的笑容 --《光阴的故事》
你曾经对我说 你永远爱着我
爱情这东西我明白 但永远是什么
姑娘你别哭泣 我俩还在一起
今天的欢乐 将是明天永恒的回忆 --《恋曲1980》