http://blog.sina.com.cn/s/blog_3fe961ae0101k4p6.html

行为模式其实非常类似于面向对象语言中的接口,至少笔者是这么理解的。OTP行为模式将一些反复出现的模式分成了两个部分,通用部分和具体应用相关的实现部分,这一过程其实就类似于面向对象编程中的抽象出接口的过程。本文给出一个OTP中最常见的行为模式的示例:通用服务器,即gen_server。

编写gen_server回调模块大致包括3相步骤:

(1) 确定回调模块的名称;

(2) 写接口函数(由客户端调用的);

(3) 在回调模块中实现gen_server的6个回调函数(由gen_server容器来调用的)。

下面给出一个《Erlang OTP并发编程实战》中的示例代码,代码通过实现gen_server的接口,进而实现一个简单的RPC服务,这个RPC服务可以允许客户端调用服务器端任意模块中导出的任意函数。并且提供了一个get_count的查询接口,用于查询当前服务器已经处理过的请求数量。另外还有start_link()和stop()用于停止服务器进程。

tr_server.erl

%%%-------------------------------------------------------------------

%%% @author Martin & Eric

%%%  [http://www.erlware.org]

%%% @copyright 2008-2010 Erlware

%%% @doc RPC over TCP server. This module defines a server process that

%%%      listens for incoming TCP connections and allows the user to

%%%      execute RPC commands via that TCP stream.

%%% @end

%%%-------------------------------------------------------------------

-module(tr_server).

-behaviour(gen_server).

%% API

-export([

start_link/1,

start_link/0,

get_count/0,

stop/0

]).

%% gen_server callbacks

-export([init/1, handle_call/3, handle_cast/2, handle_info/2,

terminate/2, code_change/3]).

-define(SERVER, ?MODULE).

-define(DEFAULT_PORT, 1055).

-record(state, {port, lsock, request_count = 0}).

%%%===================================================================

%%% API

%%%===================================================================

%%--------------------------------------------------------------------

%% @doc Starts the server.

%%

%% @spec start_link(Port::integer()) -> {ok, Pid}

%% where

%%  Pid = pid()

%% @end

%%--------------------------------------------------------------------

start_link(Port) ->

gen_server:start_link({local, ?SERVER}, ?MODULE, [Port], []).

%% @spec start_link() -> {ok, Pid}

%% @doc Calls `start_link(Port)' using the default port.

start_link() ->

start_link(?DEFAULT_PORT).

%%--------------------------------------------------------------------

%% @doc Fetches the number of requests made to this server.

%% @spec get_count() -> {ok, Count}

%% where

%%  Count = integer()

%% @end

%%--------------------------------------------------------------------

get_count() ->

gen_server:call(?SERVER, get_count).

%%--------------------------------------------------------------------

%% @doc Stops the server.

%% @spec stop() -> ok

%% @end

%%--------------------------------------------------------------------

stop() ->

gen_server:cast(?SERVER, stop).

%%%===================================================================

%%% gen_server callbacks

%%%===================================================================

init([Port]) ->

{ok, LSock} = gen_tcp:listen(Port, [{active, true}]),

{ok, #state{port = Port, lsock = LSock}, 0}.

handle_call(get_count, _From, State) ->

{reply, {ok, State#state.request_count}, State}.

handle_cast(stop, State) ->

{stop, normal, State}.

handle_info({tcp, Socket, RawData}, State) ->

do_rpc(Socket, RawData),

RequestCount = State#state.request_count,

{noreply, State#state{request_count = RequestCount   1}};

handle_info(timeout, #state{lsock = LSock} = State) ->

{ok, _Sock} = gen_tcp:accept(LSock),

{noreply, State}.

terminate(_Reason, _State) ->

ok.

code_change(_OldVsn, State, _Extra) ->

{ok, State}.

%%%===================================================================

%%% Internal functions

%%%===================================================================

do_rpc(Socket, RawData) ->

try

{M, F, A} = split_out_mfa(RawData),

Result = apply(M, F, A),

gen_tcp:send(Socket, io_lib:fwrite("~p~n", [Result]))

catch

_Class:Err ->

gen_tcp:send(Socket, io_lib:fwrite("~p~n", [Err]))

end.

split_out_mfa(RawData) ->

MFA = re:replace(RawData, "\r\n$", "", [{return, list}]),

{match, [M, F, A]} =

re:run(MFA,

"(.*):(.*)\s*\\((.*)\s*\\)\s*.\s*$",

[{capture, [1,2,3], list}, ungreedy]),

{list_to_atom(M), list_to_atom(F), args_to_terms(A)}.

args_to_terms(RawArgs) ->

{ok, Toks, _Line} = erl_scan:string("["   RawArgs   "]. ", 1),

{ok, Args} = erl_parse:parse_term(Toks),

Args.

笔者在Linux环境下运行这个程序:

1> c(tr_server).

{ok,tr_server}

2> tr_server:start_link().

{ok,<0.39.0>}

3>

这里需要再启动一个Shell,输入:

root@controller:~# telnet 127.0.0.1 1055

Trying 127.0.0.1...

Connected to 127.0.0.1.

Escape character is '^]'.

然后再回到Erlang控制台,输入:

3> tr_server:get_count().

{ok,0}

4> tr_server:stop().

ok

为什么要先用telnet连接一下1055端口呢?分析一下init([Port])这个函数的行为。init([Port])函数首先用标准库中的gen_tcp模块在指定的端口上建立一个TCP监听套接字:

{ok, LSock} = gen_tcp:listen(Port, [{active, true}]),

然后init([Port])函数返回一个三元组,包含原子ok,初始进程状态,以及数字0:

{ok, #state{port = Port, lsock = LSock}, 0}.

这个0表示超时值。将超时设置为零就是让gen_server容器在init/1结束后,立即触发一个超时,从而迫使进程在完成初始化之后第一时间处理超时消息(由handle_info/2完成)。在这里使用0的用意是唤醒服务器并执行一些指定的操作:等待创建的监听套接字上的连接。在没有接收到连接时,gen_server会一直阻塞在这里,因此如果此时发送tr_server:get_count()请求,将会得到一个timeout反馈:

** exception exit: {timeout,{gen_server,call,[tr_server,get_count]}}

tr_server所实现的RPC服务,理论上可以调用服务器端任意模块中导出的任意函数。比如,可以在telnet中输入:

init:stop().

返回:

ok

Connection closed by foreign host.

这是因为init:stop()关闭了运行着RPC服务器的整个Erlang节点。

最后,我们看看gen_server中的几个常用的回调函数是怎么定义的。打开gen_server源码(笔者的Windows系统上,这个文件位于C:\Program Files (x86)\erl5.8.5\lib\stdlib-1.17.5\src),文件的头部注释中,详细地介绍了各个接口所需要返回的参数格式,以及gen_server的执行流程。

%%% ---------------------------------------------------

%%%

%%% The idea behind THIS server is that the user module

%%% provides (different) functions to handle different

%%% kind of inputs.

%%% If the Parent process terminates the Module:terminate/2

%%% function is called.

%%%

%%% The user module should export:

%%%

%%%   init(Args)

%%%     ==> {ok, State}

%%%         {ok, State, Timeout}

%%%         ignore

%%%         {stop, Reason}

%%%

%%%   handle_call(Msg, {From, Tag}, State)

%%%

%%%    ==> {reply, Reply, State}

%%%        {reply, Reply, State, Timeout}

%%%        {noreply, State}

%%%        {noreply, State, Timeout}

%%%        {stop, Reason, Reply, State}

%%%              Reason = normal | shutdown | Term terminate(State) is called

%%%

%%%   handle_cast(Msg, State)

%%%

%%%    ==> {noreply, State}

%%%        {noreply, State, Timeout}

%%%        {stop, Reason, State}

%%%              Reason = normal | shutdown | Term terminate(State) is called

%%%

%%%   handle_info(Info, State) Info is e.g. {'EXIT', P, R}, {nodedown, N}, ...

%%%

%%%    ==> {noreply, State}

%%%        {noreply, State, Timeout}

%%%        {stop, Reason, State}

%%%              Reason = normal | shutdown | Term, terminate(State) is called

%%%

%%%   terminate(Reason, State) Let the user module clean up

%%%        always called when server terminates

%%%

%%%    ==> ok

%%%

%%%

%%% The work flow (of the server) can be described as follows:

%%%

%%%   User module                          Generic

%%%   -----------                          -------

%%%     start            ----->             start

%%%     init             <-----              .

%%%

%%%                                         loop

%%%     handle_call      <-----              .

%%%                      ----->             reply

%%%

%%%     handle_cast      <-----              .

%%%

%%%     handle_info      <-----              .

%%%

%%%     terminate        <-----              .

%%%

%%%                      ----->             reply

%%%

%%%

%%% ---------------------------------------------------

参考资料:《Erlang OTP并发编程实战》

Erlang OTP编程初体验——gen_server和行为模式的更多相关文章

  1. ASP.NET Core 6框架揭秘实例演示[01]: 编程初体验

    作为<ASP.NET Core 3框架揭秘>的升级版,<ASP.NET Core 6框架揭秘>提供了很多新的章节,同时对现有的内容进行大量的修改.虽然本书旨在对ASP.NET ...

  2. Linux下使用gcc编程初体验

    近期刚刚放弃了Windows,投入了Ubuntu 的怀抱.今天就拿一个小小的案例来做一下C语言的编译和运行流程.额,顺便说一句.本文适合那些Linux新手,不适合老鸟哈. 看完本文可以学到什么? 程序 ...

  3. GUI应用编程初体验

    不同平台的GUI实现原理是一样的. 本实验基于 windos平台. 先捋一捋概念 什么是消息队列(Message Queue)假 设一个场景:系统正在处理WM_PAINT消息,就在这时用户在键盘上敲击 ...

  4. IOS - socket 编程初体验

    #import "ViewController.h" @interface ViewController () <NSStreamDelegate, UITextFieldD ...

  5. Shell脚本编程初体验

    原文:http://linoxide.com/linux-shell-script/guide-start-learning-shell-scripting-scratch/ 作者: Petras L ...

  6. 实验1 C语言开发环境使用和编程初体验

    #include <stdio.h> #include <stdlib.h> int main() { printf ("202083290273\n2020 ,wh ...

  7. Spring Cloud Data Flow初体验,以Local模式运行

    1 前言 欢迎访问南瓜慢说 www.pkslow.com获取更多精彩文章! Spring Cloud Data Flow是什么,虽然已经出现一段时间了,但想必很多人不知道,因为在项目中很少有人用.不仅 ...

  8. Scala 深入浅出实战经典 第66讲:Scala并发编程实战初体验

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  9. 数据结构(逻辑结构,物理结构,特点) C#多线程编程的同步也线程安全 C#多线程编程笔记 String 与 StringBuilder (StringBuffer) 数据结构与算法-初体验(极客专栏)

    数据结构(逻辑结构,物理结构,特点) 一.数据的逻辑结构:指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后件关系,而与他们在计算机中的存储位置无关.逻辑结构包括: 集合 数 ...

随机推荐

  1. Mongodb总结5-通过装饰模式,用Mongodb解决Hbase的不稳定问题

    最近继续学习Mongodb的根本原因,是为了解决今天的问题.项目中用到了Hbase,生产环境服务器用了3台,但是不够稳定,每2天左右,就连不上了.重启就好了,当然,这是一个历史遗留问题.我在想,是不是 ...

  2. Java基础学习总结(51)——JAVA分层理解

    service是业务层  action层即作为控制器 DAO (Data Access Object) 数据访问   1.JAVA中Action层, Service层 ,modle层 和 Dao层的功 ...

  3. 再记AE与AO的区别与联系

    原文地址:转:ArcObjects与ArcEngine作者:梦游   ArcObjects(简称AO),一般都是指ArcGIS Desktop版本的组件开发集,即需要安装ArcGIS桌面版软件后才能安 ...

  4. Maven报错Missing artifact jdk.tools:jdk.tools:jar:1.7--转

    原文地址:http://blog.csdn.net/u013281331/article/details/40824707 在Eclipse中检出Maven工程,一直报这个错:“Missing art ...

  5. C_C++指针指针应用详解

    前言:复杂类型说明 要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其 ...

  6. 文本超出显示省略号CSS

    单行超出显示省略号 display: block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; 多行超出显示省略号 1.普 ...

  7. Mac 环境下svn服务器的配置

    Mac 环境下svn服务器的配置 本文目录 • 一.创建代码仓库,用来存储客户端所上传的代码 • 二.配置svn的用户权限 • 三.使用svn客户端功能 在Windows环境中,我们一般使用Torto ...

  8. AForge,Emgu.CV抓拍图像大小

    原文:AForge,Emgu.CV抓拍图像大小 2017年,忙忙碌碌地过去了,象往年一样,依然没有时间上CSDN,博客园. 这一年是打工以来最辛苦的一年. 这一年用了不少自己没有接触过的东西.如人脸识 ...

  9. 利用java反射将结果集封装成为对象和对象集合

    java反射机制是什么 反射机制是在运行状态中,可以知道任何一个类的属性和方法,并且调用类的属性和方法: 反射机制能够做什么 1.判断运行对象的所属类 2.构造任意一个类的对象 3.获取任意一个类的属 ...

  10. Hbase常见异常 分类: B7_HBASE 2015-02-02 16:16 412人阅读 评论(0) 收藏

    1. HBase is able to connect to ZooKeeper but the connection closes immediately hbase(main):001:0> ...