erlang gen_server牛角尖问答

我们以Module代表gen_server的callback模块
1, 实现gen_server behaviour的模块会产生一个新的process么?
毫无疑问,太会了!通过调用proc_lib:start_link/5创建process
2, gen_server:start_link/3,4中的Options参数有什么用处?
Options中只有这几个选项:{debug,Dbgs} | {timeout,Time} | {spawn_opt,SOpts},
debug是用来和sys模块相关联的;咱们通过proc_lib:start_link/5创建process的时候,如果初始化等待的时间超过了Time,那么我们的gen_server:start_link会返回{error, timeout}的错误;spawn_opt是给spawn_opt传递的参数。
3, Module:init/1的那些返回值都什么意思?
{ok, State},就不用说了,一切正常,State将为gen_server的State;
{ok, State, Timeout},也很正常,只是如果process在Timeout(>=0,ms)时间内没有收到任何消息,那么将产生一个timeout消息,这个消息要在handle_info中处理哦。
{ok, State, hibernate}, 还是正常,只是我们在process启动后,就让它先”睡眠“,因为我们知道最近一段时间内,我们还不用这个process,此process睡眠的好处就是可以最大限度的减少其内存占用,当有消息到达时,process就会”惊醒“,重新工作。
{stop, Reason},oops,出错了,process将会调用exit(Reason)退出。如果init没有预期,我们就退出吧。
ingore,既然要求忽略了,什么都不做,退出吧。
4, gen_server:multi_call/2,3,4这几个哥们是干嘛用的?
multi就是多的意思嘛,mulit_call肯定就是进行多个调用请求哦。
先说他们的关系:
gen_server:multi_call(Name, Request)
= gen_server:multi_call([node() | nodes()], Name, Request)
= gen_server:multi_call([node() | nodes()], Name, Request, infinity)
称他们为兄弟,我看称他们为三胞胎还不错。
想多个node请求本地名为Name的gen_server behaviour, 然后收集结果,返回。
其返回值格式为:{Replies,BadNodes},其中Replies为[{Node, Reply}],BadNodes,不用说就是那些没有正常返回应答的节点了。
mulit_call/4最后一个Time参数指定的是每个Reply等待的超时时间。
5, gen_server:reply/2有嘛用处?
如果在Module:handle_call/3中,我们不能返回结果,我们可以保存handle_call中的From参数,在结果生成的时候,返回给Caller。
6, Module:handle_call/3 返回{noreply,NewState}会怎样?
怎样?明明是handle_call处理的是同步的请求,处理完了就应该告诉Caller,是死是活,是成功是失败,可是你noreply,
Caller只能傻傻的等待了。等多久?默认的是5秒,gen_server:call/3就调用exit({timeout, Description}),退出了。。
7, 我用gen_server竟然死锁了?
天呀,在erlang中怎么遇到锁了?肯定是你使用不当!

name() ->
gen_server:call(?SERVER, name).
address() ->
gen_server:call(?SERVER, address).

all_info() ->
gen_server:call(?SERVER, all_info).

…..
handle_call(all_info, _From, State) ->
Name = name(),
Address = address(),
{reply, {Name, Address}, [...]

自己写一个tcp 通用服务器

这篇文章本来早就该写,一直忘记,今天写一下,为后面的mochiweb的分析做个引子。
我们想写这样一个tcp server,其绑定本地某个端口,用户可以接入实现特定的业务,比如一个傻傻的echo server,一个帮助服务器等等。。毫无疑问这个tcp的框架是相同的,想想我们一直以来怎么写tcp server:
创建socket -> 绑定端口 -> listen监听 -> accept tcp 连接 -> 处理业务 -> 关闭连接。中间可能会有多线程或者线程池等等不同的实现方式。
在erlang的世界,我们还是需要绑定端口,接受连接,处理业务,关闭连接,但是我们没有什么线程,锁的烦恼。我们为每个连接建立一个process,处理业务。因为erlang的process轻量,高效,成千上万。
我们用gen_sever behaviour来实现这个通用的tcp server,gen_server其实内部包含一个大循环,如果我们的tcp server再gen_server的循环中调用gen_tcp:accept/1,那么我们会阻塞gen_server,这肯定行不通。那么只有将gen_tcp:accept放到一个独立的process中了。
我们的generic_server中定义了一个record:

-record(server_state, {
port, % 监听的端口
loop, % 具体的逻辑处理循环
ip=any, % 绑定的ip
lsocket=null, % 监听socket
conn=0, % 当前的连接数
maxconn % 最大的连接数
}).

这个record用来记录服务器的相关信息,注释已经相当清楚。
接下来让我们实现gen_server的init/1函数(如果对gen_server不熟悉,请参看以前文章,或者erlang官方文档),这个部分非常关键:

%% gen_server初始化,创建监听socket
init(State = #server_state{port=Port}) ->
case gen_tcp:listen(Port, ?TCP_OPTIONS) of
{ok, LSocket} ->
{ok, accept(State#server_state{lsocket=LSocket})};
{error, Reason} ->
[...]

gen_server behaviour随心记录

gen_server behaviour
gen_server为最常用的一个behaviour,其定义了client/server模型的基本框架。
我们只需要定义一个“回调”(callback)的module,实现必要的export即可采用gen_server。
1.genserver:start_link/4

启动gen_server调用start_link/4,比如:gen_server:start_link({local, myserver}, myserver, arg, [])表示我们在本地节点注册一个名叫myserver的process,这个参数也可以忽略,那样就不会注册process,而一切交互采用Pid进行。
第一个参数也可以为{global,myserver},表示通过调用global:register_name/2来进行名字的注册。
第二个参数myserver为回调模块名称(atom)
第三个参数为传递给myserver:init的参数,此处为空(Term)
第四个参数为gen_server的相关选项
函数成功则返回{ok, Pid}.
start_link/4会调用myserver:init/1进行初始化工作,这里的调用是同步进行的,只有init/1执行完毕后,start_link/4才可执行完毕。
注意如果此behaviour用在监督树中,则必须使用gen_server:start_link/4启动,如果某个单独的应用可以采用gen_server:start启动。监督树中为什么使用gen_server/start_link/4其实好好想想就有答案了,因为监督树的目的就是为了维护控制worker,出错时进行必要的restart,因此只有将其link起来才能进行这些操作,否则互不相干啊。呵呵
2. myserver:init(Arg) -> {ok, State} | {stop, Reasion}

进行初始化,如果成功返回新的状态,否则gen_server停止
3. gen_server:call(Name, Term) -> Term

进行同步调用,即请求-等待结果。call内部调用myserver:handle_call/3函数。
4. gen_server:cast(Name, Term) -> void

进行异步调用,类似消息,调用后立即返回。cast内部调用myserver:handle_cast/2函数
5. gen_server的停止

如果gen_server是监督树(supervision tree)的一部分stop函数是没有必要的。gen_server会自动的被监督树停止。这里与监督树的停止策略(shutdown strategy)相关。如果停止策略为指定时间内停止,gen_server便可以在停止前做一些清理工作,首先在myserver:init/1中设置trap_exit为true(process_flag(trap_exit, true)),然后设置对应的terminate/2函数:terminate(shutdown, State)再此函数内就可以进行清理工作(因为监督树会首先向gen_server发送exit(Pid, shutdown)消息,然后等待某个超时,如果超时内Pid未停止则调用exit(Pid, kill)进行野蛮(brutal)的kill,此处参考supervision tree的shutdown strategy)。