Erlang/OTP:基于Behaviour的回调函数
原始链接:https://blog.zhustec.me/posts/erlang-otp-1-callback-based-on-behaviour
OTP 是什么
OTP 的全称是开源电信平台 (Open Telecom Platform),但是它的实际作用却不像它的名字一样只是用做电信平台,它是 Erlang 中的一套用于方便用户编写高容错性系统的框架。如果说 Erlang 的伟大之处一半来自于它的并发和分布式,那么另一半就来自于 OTP 框架。
为什么需要 OTP
在编写 Erlang 程序时,有很大一部分代码都是重复造轮子,重复编写的,而 OTP 则是提取出这些代码中的通用部分,而使用户只需要编写特定代码部分而不需要重复编写通用部分的代码。
传统的代码
比如一个简单的客户进程与服务进程通讯的例子:服务进程仅仅打印出客户进程发送过来的消息
-module(server). -export ([start/0, req/1, loop/0]). start() -> register(?MODULE, spawn_link(?MODULE, loop, [])). req(Req) -> ?MODULE ! {self(), Req}. loop() -> receive {_, stop} -> ok; {_, Msg } -> io:format("~p~n", [{ok, Msg}]), loop() end.
而现在我需要编写另外一个程序,服务进程不是打印出客户进程的请求,而是返回给客户进程,那么我需要再写一个模块
-module(server2). -export([start/0, req/1, loop/0]). start() -> register(?MODULE, spawn_link(?MODULE, loop, [])). req(Req) -> ?MODULE ! {self(), Req}. loop() -> receive {_, stop} -> ok; {From, Msg} -> From ! {ok, Msg}, loop() end.
可以看出上面两段程序作为简单的客户进程与服务进程交互的例子具有很多的相似部分。
- 服务进程启动时都需要使用
swapn_link
创建并注册到当前模块名的原子 - 提供了包装了访问服务器请求的接口
req
函数 - 服务进程最终进入无限循环
loop
中接收消息直到遇到stop
命令
而两段程序的差别仅仅是在无限循环 loop
中所接受的请求及对应的处理方式。
程序的代码可以分成两个部分
- 通用部分:在许多程序中都能使用的代码
- 特定部分:只在个别程序中使用的代码
所以我们可以将上面的程序中的代码分成两个部分
- 通用部分:
start
函数和loop
函数及期中的receive
块 - 特定部分:
rpc
客户进程请求和receive
的消息
既然上面两段程序有许多通用的代码,我们可以考虑使用代理模块来剥离出通用部分。
使用代理模块剥离出通用部分
将第一个程序修改为
-module(server). -export([start/0, req/1, handle_req/1]). start() -> proxy:start_link(?MODULE). req(Req) -> proxy:req(?MODULE, Req). handle_req(Msg, _From) -> io:format("~p~n", [{ok, Msg}]).
而代理模块看起来就像这样
-module(proxy). -export([start_link/1, req/2, loop/1]). start_link(Mod) -> register(Mod, spawn_link(?MODULE, loop, [Mod])). req(Mod, Req) -> Mod ! {Req, self()}. loop(Mod) -> receive {Msg, From} -> case Mod:handle_msg(Msg, From) of stop -> ok; _ -> loop(Mod) end end.
而此时,第二个程序就可以修改为
-module(server2). -export([start/0, req/1, handle_req/1]). start() -> proxy:start_link(?MODULE). req(Req) -> proxy:req(?MODULE, Req). handle_req(Msg, _From) -> From ! {ok, Msg}.
或者,可以再写出一个程序。
-module(server3). -export([start/0, print/1, stop/0, handle_req/1]). start() -> proxy:start_link(?MODULE). print(String) -> proxy:req(?MODULE, {print, String}). stop() -> proxy:req(?MODULE, stop). handle_req(stop, _) -> io:format("Time to die.~n"), stop; handle_req({print, String}, _) -> io:format(String); ok; handle_req(_, _) -> stop.
通过使用代理模块将程序的通用部分剥离出来的方式,简化了程序的编写。
有些人可能会问,没看出来使用代理模块后的程序代码数量减少。实际上,我这里只是写的一个简单的代理模块,而且服务程序也是简单的服务程序。在生产应用中,服务程序的启动不会是只是简单启动一个新进程运行一个无限循环函数那么简单,而是要进行若干操作并初始化服务状态,所处理的消息也包括各种同步消息、异步消息及来自其他进程的消息,另外还要处理各种异常和进行热代码更新。这样服务程序之间的通用部分与特定部分的代码比例将非常高,这个时候使用代理模块剥离出通用部分的代码将显得十分有效。
OTP 就是一套复杂的代理模块集合
上面通过使用代理模块剥离出了许多客户-服务交互程序的通用部分代码,而 OTP 就是一套复杂的、具有高容错性的代理模块的集合,它就将通用部分包装并隐藏起来,而通过调用特定程序的回调函数来执行特定部分代码。
其实上面的代理模块是我对 gen_server
模块的拙劣模仿
OTP 的简单实现原理
上面提到,OTP 将通用代码剥离出来放进代理模块中,而特定代码则使用回调函数实现。
回调函数
在上面的代码中,代理模块里的 loop
函数在接收到消息后调用 Mod:handle_msg(Msg, From)
调用用户实现的回调函数,这行语句要求用户的模块必须实现并导出了 handle_msg/2
函数,但是如果用户没有实现这个函数的话,运行时就会崩溃掉。因此,需要一种方式通知用户,如果你要让我代理你,你需要实现我所要求的回调函数,这种方式就是使用 behaviour 属性。
behaviour 属性
在 Erlang 中可以为模块添加自定义的属性。
-module(test_tag). -tag1(tagname1). -tag2(tagname2). -tag3(tagname3). % Run in Erlang Shell % % c(test_tag). % % test_tag:module_info(attributes). % => [{vsn, [blablabla]}, % => {tag1, tagname1}, % => {tag2, tagname2}, % => {tag3, tagname3}]
所以可以使用 -behaviour(Behaviour).
添加一个 behaviour 的属性
-module(test_be). -behaviour(be_example). % Run in Erlang Shell % % c(test_be). % % test_be:module_info(attributes). % => [{vsn, [blablabla]}, % => {behaviour, [be_example]}]
Behaviour 必须是已经加载的或者在加载路径里已经编译好的模块名,它的意思是:当前模块在行为上类似于 be_example
。
实际上,behaviour 的作用是用来确保一个模块必须导出一系列被称作为回调函数的函数。比如 test_be
模块拥有 {behaviour, be_example}
的属性,则 test_be
模块必须导出 be_example
模块所要求的回调函数,如果没有导出相关的函数,在编译时将会报出警告。
一个模块可以具有多个 behaviour 属性,这个模块必须具有这些模块所指定的所有回调函数。
-module(test_be). -behaviour(application). -behaviour(supervisor). % Run in Erlang Shell % % c(test_be). % Warning: undefined callback function start/2 (behaviour 'application') % Warning: undefined callback function stop/1 (behaviour 'application') % Warning: undefined callback function init/1 (behaviour 'supervisor') % % test_be:module_info(attributes). % => [{vsn, [blablabla]}, % => {behaviour, [application]}, % => {behaviour, [supervisor]}]
上面的例子中,当前模块必须导出模块 application
所要求的回调函数,同时也必须导出模块 supervisor
所要求的回调函数。当然在实际使用时不建议这么使用,这里只是作为例子来解释。
定义 Behaviour
上面说到,Behaviour 实际上就是声明了一些必须被导出的函数,声明的方式有两种
定义并导出 behaviour_info/1
函数
方法的返回值是由函数名,函数参数个数组成的元组的列表
-module(be_example). -export([behaviour_info/1]). -spec behaviour_info(callbacks) -> [{FunName, Arity}] when FunName :: atom(), Arity :: integer(). behaviour_info(callbacks) -> [{init, 1}, {handle_msg, 2}, {handle_info, 2}].
当编译器编译到语句 -behaviour(Behaviour).
时,编译器会调用
-Behaviour:behaviour_info(callbacks).
然后比较结果与模块的导出函数,如果有 callbacks
没有被导出,就会产生一个警告。
定义 callback
属性
这个属性会自动生成 behaviour_info/1
函数
-module(be_example). -callback init(Args :: term()) -> {ok, State :: term()} | {error, Reason :: term()}. -callback handle_msg(Msg :: term(), From :: pid()) -> {ok, Reply :: term()} | {stop, Reason :: term()}. -callback handle_info(Info :: term()) -> ok | {stop, Reason :: term()}.
打开 Erlang Shell 编译并使用这个模块
c(be_example).be_example:behaviour_info(callbacks). % => [{init,1},{handle_msg,2},{handle_info,1}] be_example:module_info(attributes). % => [{vsn,[blablabla]}, % => {callback,[{ {init,1}, % => [{type,3,'fun', % => [blablabla]}]}]}]}]
建议使用第二种方式,因为从上面的例子就能看出来了使用 callbacks
属性可以定义回调函数的参数类型和返回值类型。通常只能在比较老的代码中才能看到第一种方式了,而现在标准库使用的都是第二种方法。
本文标题:Erlang/OTP: 基于 Behaviour 的回调函数
文章作者:Zhustec
原始链接:https://blog.zhustec.me/posts/erlang-otp-1-callback-based-on-behaviour
许可协议:"署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。
转载于:https://www.cnblogs.com/buxizhizhoum/p/6525586.html
Erlang/OTP:基于Behaviour的回调函数相关推荐
- 【Erlang/OTP入门】基于进程的并发编程和分布式
引言 在参加区统考的前一天开始写这篇文章,开学后时间真的紧迫了很多. 我最近玩Erlang只是一个偶然(?).这一切的开始是我某天看到一本名为<Erlang and OTP in Actio ...
- 回调函数 线程_从0实现基于Linux socket聊天室-多线程服务器一个很隐晦的错误-2...
根据 <0 基于socket和pthread实现多线程服务器模型>所述,server创建子线程的时候用的是以下代码: pconnsocke = (int *) malloc(sizeof( ...
- 基于c++和asio的网络编程框架asio2教程基础篇:2、各个回调函数的触发顺序和执行流程
基于c++和asio的网络编程框架asio2教程基础篇:2.各个回调函数的触发顺序和执行流程 以tcp举例: tcp服务端流程: #include <asio2/asio2.hpp>int ...
- 基于MFC相机采集的实现与采集回调函数的应用实例
整理工程代码的时候发现有一段代码(因为按照调用关系写流程图,这一段没有被写上): //--------------------------------------------------------- ...
- ESP8266基于MicroPython的TCP socket回调函数实现案例
参考链接 其它参考 python socket和简单tcp通信实现 - Katherina.K - 博客园 GitHub - robert-hh/FTP-Server-for-ESP8266-ESP3 ...
- Erlang/OTP设计原则(文档翻译)
http://erlang.org/doc/design_principles/des_princ.html 图和代码皆源自以上链接中Erlang官方文档,翻译时的版本为20.1. 这个设计原则,其实 ...
- Erlang/OTP 构建 Application
在Erlang/OTP ,Application表示作为一个单元,可以启动和停止,执行一些特定功能的组件,并可以在其它系统中重新使用.Application控制器的模块接口,是在每一个Erlang ...
- Erlang/OTP并发编程实战
<Erlang/OTP并发编程实战> 基本信息 原书名:Erlang and OTP in Action 作者: (美)洛根(Logan,M.) 梅里特(Merritt,E.) (瑞典) ...
- Erlang/OTP之gen_fsm行为模式
2019独角兽企业重金招聘Python工程师标准>>> 1. Fsm 称为 有限状态机,举个例子,游戏中的怪物称为NPC,NPC一般有几种状态,比如:静止,移动,死亡,被攻击,攻击英 ...
最新文章
- javascript函数嵌套时arguments的问题
- APP和网站应该选择云主机还是服务器呢?
- 上传大数据到SharePoint 2010
- boost::geometry模块多边形叠加示例
- C++的文艺复兴: Why C++? 王者归来
- 2021-2025年中国制药废物处理与管理行业市场供需与战略研究报告
- 文件系统FAT32、NTFS、exFAT的对比
- 双眼融合训练一个月_视觉融合功能的四种训练方法
- 如何去掉input type=file中的选择文件
- 无法在驱动器1分区上安装W ndOwS,安装win10提示无法在驱动器0分区上安装windows
- 2019hbcpc部分题解
- 拥有百万粉丝的大牛讲述学Android的历程程。看看你缺了哪些?
- linux内部网关协议igp,OSPF基础知识概述
- 如何让jar包显示Java图标
- 汽车嵌入式软件自动化测试的方法及推荐工具
- UVA 1598 Exchange
- 传奇微端大带宽服务器如何选择
- 安卓电池校正_笔记本电池损耗55%后一波三折的挽救经历
- 基于envoy的分布式网关-contour
- word中插入图片只显示底边,其他看不到,插入公式显示不全