<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Gen_server on cnDenis的笔记</title><link>https://blog.cndenis.com/tags/gen_server.html</link><description>Recent content in Gen_server on cnDenis的笔记</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sat, 07 Jun 2014 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.cndenis.com/tags/gen_server/index.xml" rel="self" type="application/rss+xml"/><item><title>【Erlang】gen_server terminate的坑</title><link>https://blog.cndenis.com/Erlang/2014/06/pitfall_on_gen_server_terminate.html</link><pubDate>Sat, 07 Jun 2014 00:00:00 +0000</pubDate><guid>https://blog.cndenis.com/Erlang/2014/06/pitfall_on_gen_server_terminate.html</guid><description>&lt;p&gt;gen_server进程结束时, 会调用&lt;code&gt;terminate&lt;/code&gt;函数, 但这并不是在任何情况下都成立的.&lt;/p&gt;
&lt;p&gt;当gen_server进程主动关闭时, 也就是在回调函数 &lt;code&gt;handle_xxx&lt;/code&gt; 中返回 &lt;code&gt;{stop...}&lt;/code&gt;
的时候, &lt;code&gt;terminate&lt;/code&gt; 是必然被调用的.&lt;/p&gt;
&lt;p&gt;当gen_server进程处于监控树中, 被其监控进程关闭时, 情况就不一样了.
只有在这个进程设置了捕获退出信号, 即 &lt;code&gt;process_flag(trap_exit, true)&lt;/code&gt;,
并且其开启选项中设置了关闭超时时间, 而不是 &lt;code&gt;brutal_kill&lt;/code&gt;, &lt;code&gt;terminate&lt;/code&gt;才会被执行.&lt;/p&gt;
&lt;p&gt;换句话说, &lt;strong&gt;不捕获退出信号的gen_server被其监控进程关闭时, 会直接死掉,
不执行&lt;code&gt;terminate&lt;/code&gt;函数!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;看一下 &lt;code&gt;gen_server&lt;/code&gt; 的源代码关于消息处理的部分:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-erlang" data-lang="erlang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;decode_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Mod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Hib&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nv"&gt;Msg&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;From&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Req&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;handle_system_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;From&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;MODULE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Mod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;Hib&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;&amp;#39;EXIT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;terminate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Mod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="nv"&gt;Msg&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="nv"&gt;Debug&lt;/span&gt; &lt;span class="o"&gt;=:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;handle_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Mod&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="nv"&gt;Msg&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;Debug1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;handle_debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;print_event&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;handle_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Mod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Debug1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看出, 当它的父进程执行 &lt;code&gt;erlang:exit(Pid, Reason) &lt;/code&gt;时,
也就是给它发送 &lt;code&gt;{'EXIT', Parent, Reason}&lt;/code&gt;,
gen_server会调用 &lt;code&gt;terminate&lt;/code&gt; 并结束自己.
gen_server源代码中并没有设置 &lt;code&gt;trap_exit&lt;/code&gt;, 也就是默认是不捕获退出信号的,
如果用户不自己设置, 进程一旦接收到 &lt;code&gt;{'EXIT', Parent, Reason}&lt;/code&gt; 就会立即退出,
没有机会运行 &lt;code&gt;terminate&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;从这段代码也可以看出, 设置 &lt;code&gt;trap_exit&lt;/code&gt; 后, 用户并不需要而且没办法在 &lt;code&gt;handle_info&lt;/code&gt;
里处理来自监控树的父进程的退出信号, gen_server在这个消息在到达 &lt;code&gt;handle_info&lt;/code&gt;
之前就已经处理了, 直接调用了 &lt;code&gt;terminate&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;因此, 要确保监控树中的gen_server在进程结束时执行 &lt;code&gt;terminate&lt;/code&gt;,
需要设置 &lt;code&gt;process_flag(trap_exit, true)&lt;/code&gt;,
并且要在启动参数中, 设置足够长的结束等待时间.&lt;/p&gt;
&lt;p&gt;参考:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="http://mazenharake.wordpress.com/2010/10/31/9-erlang-pitfalls-you-should-know-about/" target="_blank" rel="noopener"
 &gt;9 Erlang pitfalls you should know about&lt;/a&gt;, 其中第7条讲的就是这个问题, 有试验代码.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="http://www.qingliangcn.com/2010/08/erlang-otp%E4%B9%8Bterminate-%E6%B7%B1%E5%85%A5%E5%88%86%E6%9E%90/" target="_blank" rel="noopener"
 &gt;Erlang OTP之terminate 深入分析&lt;/a&gt;, 庆亮写的分析, 也有试验代码.
其中对于 &lt;code&gt;simple_one_for_one&lt;/code&gt; 的描述不尽正确, 在目前的R16B中,
&lt;code&gt;simple_one_for_one&lt;/code&gt; 的子进程信息是存在 &lt;code&gt;#state.dynamics&lt;/code&gt; 中的,
只要处理时间足够, 关闭监控树是会正常关闭所有子进程的.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>gen_server笔记</title><link>https://blog.cndenis.com/Erlang/2013/04/gen_server%E7%AC%94%E8%AE%B0.html</link><pubDate>Wed, 24 Apr 2013 00:00:00 +0000</pubDate><guid>https://blog.cndenis.com/Erlang/2013/04/gen_server%E7%AC%94%E8%AE%B0.html</guid><description>&lt;p&gt;gen_server是erlang的OTP框架中最常用的“行为模式”了吧，至少几本erlang教材都是首
先介绍这个。&lt;/p&gt;
&lt;strike&gt;
这东西用来做什么的呢？或者说，为什么要用这东西呢？由于我接触这东西不过几天，
理解尚非常粗浅，就我看来，用gen_server有以下几个好处：
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;面向对像，封装数据与方法&lt;/em&gt;。gen_server内部需要维护一个状态State，并提供各种函数
给别人调用，就类似于其他语言中的“类”一样，把数据和方法封装在一起，防止数据被非法
改动。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;简化调用，屏蔽通讯&lt;/em&gt;。erlang中如果要自己实现远程过程调用（RPC）的话，需要自己
定义消息格式，自己编写封装与解包的代码，还要处理各种异常问题，这些gen_server
都帮我们做好了，只需要像平常使用函数一样直接调就用行了，省时省力还不出错。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;热代码替换等高级功能&lt;/em&gt;。 热代码替换是erlang大力宣传的一项特色功能，不停机维护
在生产上是非常美妙的事。不过这是高级功能，初学暂时用不上，书上也没多讲。
&lt;/strike&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="gen_server模板"&gt;gen_server模板
&lt;/h2&gt;&lt;p&gt;gen_server是可以使用模板来写的，如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-erlang" data-lang="erlang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;behaviour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen_server&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;%% API
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="p"&gt;([]).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;%% gen_server callbacks
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handle_call&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handle_cast&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handle_info&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;terminate&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code_change&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;SERVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;MODULE&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nn"&gt;gen_server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;SERVER&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;MODULE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;handle_call&lt;/span&gt;&lt;span class="p"&gt;(_&lt;/span&gt;&lt;span class="nv"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="nv"&gt;From&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Reply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;handle_cast&lt;/span&gt;&lt;span class="p"&gt;(_&lt;/span&gt;&lt;span class="nv"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;handle_info&lt;/span&gt;&lt;span class="p"&gt;(_&lt;/span&gt;&lt;span class="nv"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;terminate&lt;/span&gt;&lt;span class="p"&gt;(_&lt;/span&gt;&lt;span class="nv"&gt;Reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;code_change&lt;/span&gt;&lt;span class="p"&gt;(_&lt;/span&gt;&lt;span class="nv"&gt;OldVsn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="nv"&gt;Extra&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;含义如下：&lt;/p&gt;
&lt;p&gt;-module 是模块名，就是文件名。&lt;/p&gt;
&lt;p&gt;-behaviour 是指定“行为模式”，在这里就是gen_server，作用在于用于检查这个模块是否
实现gen_server的所有接口。也就是 &lt;code&gt;init/1&lt;/code&gt;, &lt;code&gt;handle_call/3&lt;/code&gt;, &lt;code&gt;handle_cast/2&lt;/code&gt;,
&lt;code&gt;handle_info/2&lt;/code&gt;, &lt;code&gt;terminate/2&lt;/code&gt;, &lt;code&gt;code_change/3&lt;/code&gt;这六个函数。缺少的话编译器就会
报错。&lt;/p&gt;
&lt;p&gt;-export 有两个，第一个里面写的是API，也就是供别人调用的接口，第二个是gen_server
的接口，就是上面说的那六个。其实要写到同一个export里也可以，不过分开写比较清楚。&lt;/p&gt;
&lt;p&gt;接下来就是各API函数，一般来说就是封装、调用各用回调函数的包装函数。&lt;/p&gt;
&lt;p&gt;再往后就是六个回调函数的具体实现。回调函数是负责具体干活的。&lt;/p&gt;
&lt;p&gt;大概可以做这么个比喻吧，如果整个模块是个昆虫，API是就头，gen_server就是身子，而
回调函数是六条腿。你告诉头说要去哪里，腿就运动起来，整条虫子就跑起来了。身子是
把各条腿粘起来，否则单独的腿只能在原地抽筋，哪都去不了。&lt;/p&gt;
&lt;h2 id="启动服务器"&gt;启动服务器
&lt;/h2&gt;&lt;p&gt;用来启动服务器的有&lt;code&gt;start/3&lt;/code&gt;,&lt;code&gt;start/4&lt;/code&gt;,&lt;code&gt;start_link/3&lt;/code&gt;,&lt;code&gt;start_link/4&lt;/code&gt;这四个函数。
使用这些start函数之后，就会产生一个新的进程，也就是一个gen_server服务器。这些
start函数的正常情况下返回值是&lt;code&gt;{ok,Pid}&lt;/code&gt;，&lt;code&gt;Pid&lt;/code&gt;就是这个新进程的进程号。
带link与不带的区别在于是否跟父进程建立链接，换种说法是，新启动的进程死掉后，
会不会通知启动他的进程（父进程）。&lt;/p&gt;
&lt;p&gt;start函数可以四个参数&lt;code&gt;(ServerName, Module, Args, Options)&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;第一个参数&lt;code&gt;ServerName&lt;/code&gt;是服务名，
是可以省掉的。具有相同服务名的模块在一个节点中只能启动一次，重复启动会报错，为
&lt;code&gt;{error, {already_started, Pid}}&lt;/code&gt;。具有服务名的服务进程可以使用服务名来调用，
没有服务名的只能通过进程号pid来调用了。通常有名字的服务进程会使用模块名做为
服务名，即上面模板中定义的宏&lt;code&gt;-define(SERVER, ?MODULE)&lt;/code&gt;，然后在需要使用服务名的
地方填入&lt;code&gt;?SERVER&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第二个参数&lt;code&gt;Module&lt;/code&gt;是模块名，一般而言API和回调函数是写在同一个文件里的，所以就用
&lt;code&gt;?MODULE&lt;/code&gt;，表示本模块的模块名。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第三个参数&lt;code&gt;Args&lt;/code&gt;是回调函数&lt;code&gt;init/1&lt;/code&gt;的参数，会原封不动地传给&lt;code&gt;init/1&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第四个参数&lt;code&gt;Options&lt;/code&gt;是一些选项，可以设置debug、超时等东西。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;start是对应的回调函数是&lt;code&gt;init/1&lt;/code&gt;，一般来说是进行服务器启动后的一些初始化的工作，
并生成初始的状态State，正常返回是{ok, State}。这个State是贯穿整个服务器，
并把所有六个回调函数联系起来的纽带。它的值最初由&lt;code&gt;init/1&lt;/code&gt;生成，
此后可以由三个handle函数修改，每次修改后又要放回返回值中，
供下一个被调用的handle函数使用。
如果&lt;code&gt;init/1&lt;/code&gt;返回&lt;code&gt;ignore&lt;/code&gt;或&lt;code&gt;{stop, Reason}&lt;/code&gt;，则会中止服务器的启动。&lt;/p&gt;
&lt;p&gt;有一点细节要注意的是，API函数和回调函数虽然习惯上是写在同一个文件中，但执行函数
的进程却通常是不一样的。在上面的模板中，&lt;code&gt;start_link/0&lt;/code&gt;中使用&lt;code&gt;self()&lt;/code&gt;的话，显示
的是调用者的进程号，而在&lt;code&gt;init/1&lt;/code&gt;中使用&lt;code&gt;self()&lt;/code&gt;的话，显示的是服务器的进程号。&lt;/p&gt;
&lt;h2 id="使用服务器"&gt;使用服务器
&lt;/h2&gt;&lt;p&gt;三个handle开头的回调函数对应着三种不同的使用服务器的方式。如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gen_server:call ------------- handle_call/3
gen_server:cast ------------- handle_cast/2
用！向服务进程发消息------------- handle_info/2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;call是有返回值的调用；cast是无返回值的调用，即通知；而直接向服务器进程发的
消息则由handle_info处理。&lt;/p&gt;
&lt;h3 id="call"&gt;call
&lt;/h3&gt;&lt;p&gt;call是有返回值的调用，也是所谓的同步调用，进程会在调用后一直等待直到回调函数返回为止。
它的函数形式是 &lt;code&gt;gen_server:call(ServerRef, Request, Timeout) -&amp;gt; Reply&lt;/code&gt;，&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一个参数&lt;code&gt;ServerRef&lt;/code&gt;是被调用的服务器，如果是服务器名，或是服务器的pid。&lt;/li&gt;
&lt;li&gt;第二个参数&lt;code&gt;Request&lt;/code&gt;会直接传给回调函数&lt;code&gt;handle_call&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;最后一个参数&lt;code&gt;Timeout&lt;/code&gt;是超时，是可以省略的，默认值是5秒。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在多节点的情况下，还有机会使用到&lt;code&gt;multi_call&lt;/code&gt;，用来向各节点上的同具有相同注册名
的服务进程发起调用。（这个函数在文档上的表述有点让人难以理解，详见
&lt;a class="link" href="http://erlang.2086793.n4.nabble.com/Question-about-gen-server-multi-call-2-td2104905.html" target="_blank" rel="noopener"
 &gt;这里&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;call是用来指挥回调函数&lt;code&gt;handle_call/3&lt;/code&gt;干活的。具体形式为
&lt;code&gt;handle_call(Request, From, State)&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一个参数&lt;code&gt;Request&lt;/code&gt;是由call传进来的，是写程序时关注和处理的重点。&lt;/li&gt;
&lt;li&gt;第二个参数&lt;code&gt;From&lt;/code&gt;是gen_server传进来的，是调用的来源，也就是哪个进程执行了call。
&lt;code&gt;From&lt;/code&gt;的形式是&lt;code&gt;{Pid, Ref}&lt;/code&gt;，&lt;code&gt;Pid&lt;/code&gt;是来源进程号，而&lt;code&gt;Ref&lt;/code&gt;是调用的标识，每一次调用
都不一样，用以区别。有了Pid，在需要向来源进程发送消息时就可以使用，但由于call
是有返回值的，一般使用返回值传递数据就好。&lt;/li&gt;
&lt;li&gt;第三个参数&lt;code&gt;State&lt;/code&gt;是服务器的状态，这是由init或是其他的handle函数生成的，可以根据需要进
行修改之后，再放回返回值中。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;call对应的回调函数&lt;code&gt;handle_call/3&lt;/code&gt;在正常情况下的返回值是&lt;code&gt;{reply, Reply, NewState}&lt;/code&gt;，
&lt;code&gt;Reply&lt;/code&gt;会作为call的返回值传递回去，&lt;code&gt;NewState&lt;/code&gt;则会作为服务器的状态。
另外还可以使用&lt;code&gt;{stop, Reasion, State}&lt;/code&gt;中止服务器运行，这比较少用。&lt;/p&gt;
&lt;p&gt;使用call要小心的是，&lt;strong&gt;两个服务器进程不能互相call&lt;/strong&gt;，不然会死锁。&lt;/p&gt;
&lt;h3 id="cast"&gt;cast
&lt;/h3&gt;&lt;p&gt;cast是没有返回值的调用，一般把它叫做通知。它是一个“异步”的调用，调用后会直接收到
&lt;code&gt;ok&lt;/code&gt;，无需等待回调函数执行完毕。&lt;/p&gt;
&lt;p&gt;它的形式是&lt;code&gt;gen_server:cast(ServerRef, Request)&lt;/code&gt;。参数含义
与call相同。由于不需要等待返回，所以没必要设置超时，没有第三个参数。&lt;/p&gt;
&lt;p&gt;在多节点的情况下，可以用&lt;code&gt;abcast&lt;/code&gt;，向各节点上的具有指定名字的服务进程发通知。
（奇怪的是为啥为不叫&lt;code&gt;multi_cast&lt;/code&gt;，明明长得跟&lt;code&gt;multi_call&lt;/code&gt;很像的）&lt;/p&gt;
&lt;p&gt;cast们对应的回调函数是&lt;code&gt;handle_cast/2&lt;/code&gt;，具体为：&lt;code&gt;handle_cast(Msg, State)&lt;/code&gt;。
第一个参数是由cast传进去的，第二个是服务器状态，和call类似，不多说。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;handel_cast/2&lt;/code&gt;的返回值通常是&lt;code&gt;{noreply, NewState}&lt;/code&gt;，这可以用来改变服务器状态，
或是&lt;code&gt;{stop, Reason, NewState}&lt;/code&gt;，这会停止服务器。通常来说，停止服务器的命令用
cast来实现比较多。&lt;/p&gt;
&lt;h3 id="原生消息"&gt;原生消息
&lt;/h3&gt;&lt;p&gt;原生消息是指不通过call或cast，直接发往服务器进程的消息，有些书上译成“带外消息”。
比方说网络套接字socket发来的消息、别的进程用!发过来的消息、跟服务器建立链接的进程死掉了，
发来&lt;code&gt;{'EXIT', Pid, Why}&lt;/code&gt;等等。一般写程序要尽量用API，不要直接用!向服务器进程发消息，
但对于socket一类的依赖于消息的应用，就不得不处理原生消息了。&lt;/p&gt;
&lt;p&gt;原生消息使用&lt;code&gt;handle_info/2&lt;/code&gt;处理，具体为&lt;code&gt;handle_info(Info, State)&lt;/code&gt;。其中Info是
发过来的消息的内容。回复和&lt;code&gt;handle_cast&lt;/code&gt;是一样的。&lt;/p&gt;
&lt;h1 id="停止服务器"&gt;停止服务器
&lt;/h1&gt;&lt;p&gt;上面介绍的handle函数返回{stop,&amp;hellip;}，就会使用回调函数&lt;code&gt;terminate/2&lt;/code&gt;进行扫尾工作。
典型的如关闭已打开的资源、文件、网络连接，打log做记录，通知别的进程“我要死啦”，
或是“信春哥，满血复活”：利用传进来的状态State重新启动服务器。&lt;/p&gt;
&lt;p&gt;最简单的就是啥都不干，返回ok就好了。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;参考资料和资源：&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="http://www.erlang.org/doc/man/gen_server.html" target="_blank" rel="noopener"
 &gt;gen_server的文档&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="http://www.erlang.org/doc/design_principles/gen_server_concepts.html" target="_blank" rel="noopener"
 &gt;OTP设计原则中gen_server的介绍&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="更新2013年9月13日"&gt;更新(2013年9月13日)
&lt;/h4&gt;&lt;p&gt;虽然笔记跟抄书没啥区别, 但抄一遍总会加强记忆的, 所以还是贴出来.&lt;/p&gt;
&lt;p&gt;call 操作会阻塞, 而且有死锁的可能性, 用起来要小心.&lt;/p&gt;</description></item></channel></rss>