《Erlang程序设计》第十六章 OTP概述
第十六章 OTP概述
Table of Contents
- 第十六章 OTP概述
- 16.1 通用服务器程序的进化路线
- 16.1.1 server1: 原始服务器程序
- 16.1.2 server2: 支持事务的服务器程序
- 16.1.3 server3: 支持热代码替换的服务器程序
- 16.1.4 server4: 同时支持事务和热代码替换
- 16.1.5 server5: 压轴好戏
- 16.2 gen_server起步
- 16.2.1 第一步: 确定回调模块的名称
- 16.2.2 第二步: 写接口函数
- 16.2.3 第三步: 编写回调函数
- 16.3 gen_server回调的结构
- 16.3.1 启动服务器程序时发生了什么
- 16.3.2 调用服务器程序时发生了什么
- 16.3.3 调用和通知
- 16.3.4 发送给服务器的原生消息
- 16.3.5 Hasta La Vista, Baby
- 16.3.6 热代码替换
- 16.1 通用服务器程序的进化路线
第十六章 OTP概述
16.1 通用服务器程序的进化路线
16.1.1 server1: 原始服务器程序
服务端实现
-module(server1). -export([start/2, rpc/2]).%% 启动服务 start(Name, Mod) ->%% 注册进程名为Name, 并在启动进程时完成模块Mod的初始化并在调用loop进行监测register(Name, spawn(fun() ->loop(Name, Mod, Mod:init()) end)).%% 远程调用 rpc(Name, Request) ->%% 向指定的进程发送请求, 使用self()获得自身的Pid, 用于识别请求者Name ! {self(), Request},receive%% 接收到处理结果后输出{Name, Response} ->Responseend.loop(Name, Mod, State) ->receive%% 接收到请求后的处理{From, Request} ->%% 调用Mod的handle函数进行处理{Response, State1} = Mod:handle(Request, State),%% 将处理结果发送给请求者From ! {Name, Response},loop(Name, Mod, State1)end.
回调程序实现
-module(name_server). -import(server1, [rpc/2]). -export([init/0, add/2, whereis/1, handle/2]).%% 对外提供的功能函数, 会通过远程调用向指定的服务名发送请求 add(Name, Place) ->rpc(name_server, {add, Name, Place}). whereis(Name) ->rpc(name_server, {whereis, Name}).%% 模块初始化, 创建新的字典 init() ->dict:new().%% 处理函数, 添加和查询 handle({add, Name, Place}, Dict) ->{ok, dict:store(Name, Place, Dict)}; handle({whereis, Name}, Dict) ->{dict:find(Name, Dict), Dict}.
运行结果
1> c(server1). {ok,server1} 2> c(name_server). {ok,name_server} 3> server1:start(name_server, name_server). true 4> name_server:add(joe, "at home"). ok 5> name_server:whereis(joe). {ok,"at home"}
处理流程:
1. server1:start(name_server, name_server). 启动进程, 完成name_server模块的初始化, 指定使用name_server的handle函数处理请求, 最后将进程注册为name_server2. name_server:add(joe, "at home"). 功能函数相当于是一个接口, 将数据封装为{add, joe, "at home"}格式, 调用rpc提交请求3. rpc(Name, Request)Name ! {self(), Request} 获取自身进程ID后将数据发送给name_server进程4. loop(Name, Mod, State) {Response, State1} = Mod:handle(Request, State) 接收到请求后调用name_server:handle处理请求5. handle({add, Name, Place}, Dict) 回调函数, 相当于接口的具体实现, 向字典中添加数据6. loop(Name, Mod, State) From ! {Name, Response} 将处理结果发送给请求者7. rpc(Name, Request) {Name, Response} -> Response 打印处理结果
16.1.2 server2: 支持事务的服务器程序
服务端实现
-module(server2). -export([start/2, rpc/2]).%% 启动服务 start(Name, Mod) ->%% 注册进程名为Name, 并在启动进程时完成模块Mod的初始化并在调用loop进行监测register(Name, spawn(fun() ->loop(Name, Mod, Mod:init()) end)).%% 远程调用 rpc(Name, Request) ->%% 向指定的进程发送请求, 使用self()获得自身的Pid, 用于识别请求者Name ! {self(), Request},receive%% 添加了出错处理{Name, crash} ->exit(rpc);%% 接收到处理结果后输出{Name, ok, Response} ->Responseend.loop(Name, Mod, OldState) ->receive%% 接收到请求后的处理{From, Request} ->%% 对Mod的handle函数调用添加异常处理try Mod:handle(Request, OldState) of{Response, NewState} ->From ! {Name, ok, Response},loop(Name, Mod, NewState)catch_:Why ->%% 发生异常则打印异常信息log_the_error(Name, Request, Why),From ! {Name, crash},%% 保留旧的状态loop(Name, Mod, OldState)endend.log_the_error(Name, Request, Why) ->io:format("Server ~p request ~p ~n caused exception ~p~n", [Name, Request, Why]).
16.1.3 server3: 支持热代码替换的服务器程序
在服务端添加了替换代码的函数
swap_code(Name, Mod) ->rpc(Name, {swap_code, Mod}).loop(Name, Mod, OldState) ->receive{From, {swap_code, NewCallBackMod}} ->From ! {Name, ack},%% 改变loop循环用于处理请求的模块loop(Name, NewCallBackMod, OldState);{From, Request} ->{Response, NewState} = Mod:handle(Request, OldState),From ! {Name, Response},loop(Name, Mod, NewState)end.
运行结果:
1> server3:start(name_server, name_server1). true 2> name_server1:add(joe, "at home"). ok 3> name_server1:add(helen, "at work"). ok 4> c(new_name_server). {ok,new_name_server} 5> server3:swap_code(name_server, new_name_server). ack 6> new_name_server:all_names(). [joe,helen] 7> new_name_server:delete(joe). ok 8> new_name_server:all_names(). [helen] 9> new_name_server:whereis(helen). {ok,"at work"}
16.1.4 server4: 同时支持事务和热代码替换
显然, 将server2中的异常捕获处理添加到server3中即可。
16.1.5 server5: 压轴好戏
空服务器的实现
-module(server5). -export([start/0, rpc/2]).%% 启动进程 start() ->spawn(fun() ->wait() end).%% 等待接收指令成为某种服务 wait() ->receive{become, F} ->F()end.%% 远程调用 rpc(Pid, Q) ->%% 由服务进程处理请求Pid ! {self(), Q},receive%% 打印处理结果{Pid, Reply} ->Replyend.
具体服务的一个实现
-module(my_fac_server). -export([loop/0]).%% 阶乘服务 loop() ->receive%% 可以接收数字计算其阶乘{From, {fac, N}} ->From ! {self(), fac(N)},loop();%% 也可以变成另外一种服务{become, Something} ->Something()end.fac(0) ->1; fac(N) ->N * fac(N-1).
运行结果:
1> c(server5). {ok,server5} 2> Pid = server5:start(). <0.52.0> 3> c(my_fac_server). {ok,my_fac_server} 4> Pid ! {become, fun my_fac_server:loop/0}. {become,#Fun<my_fac_server.loop.0>} 5> server5:rpc(Pid, {fac, 30}). 265252859812191058636308480000000
运行流程:
1. 启动进程, 等待接收指令成为某种服务 Pid = server5:start(). start() -> spawn(fun() -> wait() end).2. 接收指令, 成为可以计算阶乘的服务 Pid ! {become, fun my_fac_server:loop/0}. 3. 服务调用 server5:rpc(Pid, {fac, 30}).4. rpc(Pid, Q) %% 由服务进程处理请求 Pid ! {self(), Q} 5. 处理请求 loop() ->receive{From, {fac, N}} ->From ! {self(), fac(N)}... 6. 接收结果并输出 rpc(Pid, Q)receive{Pid, Reply} -> Replyend.
16.2 gen_server起步
16.2.1 第一步: 确定回调模块的名称
模拟支付系统, 模块命名为my_bank
16.2.2 第二步: 写接口函数
%% 启动本地服务器 %% gen_server:start_link({local, Name}, Mod, _) start() ->gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).%% 远程调用 %% gen_server:call(Name, Term) stop() ->gen_server:call(?MODULE, stop).%% 开一个新账户 new_account(Who) ->gen_server:call(?MODULE, {new, Who}). %% 存钱 deposit(Who, Amount) ->gen_server:call(?MODULE, {add, Who, Amount}). %% 取钱 withdraw(Who, Amount) ->gen_server:call(?MODULE, {remove, Who, AMount}).
16.2.3 第三步: 编写回调函数
%% init([]) -> {ok, State} %% 必须实现的回调函数, 用于模块初始化 %% 这里返回一个ETS表 init([]) ->{ok, ets:new(?MODULE, [])}.%% handle_call(_Request, _From, State) -> {reply, Reply, State} %% 必须实现的回调函数, 用于gen_server:call时回调使用%% 添加用户 handle_call({new, Who}, _From, Tab) ->%% 查询ETS表中相关用户是否存在%% 不存在则插入ETS表并提示欢迎信息%% 存在则提示已经存在此用户Reply = case ets:lookup(Tab, Who) of[] ->ets:insert(Tab, {Who, 0}),{welcome, Who};[_] ->{Who, you_already_are_a_customer}end,{reply, Reply, Tab};%% 用户存钱 handle_call({add, Who, X}, _From, Tab) ->%% 首先查询用户是否存在%% 存在则将存款累加后重新插入ETS表并给出提示信息Reply = case ets:lookup(Tab, Who) of[] ->not_a_customer;[{Who, Balance}] ->NewBalance = Balance + X,ets:insert(Tab, {Who, NewBalance}),{thanks, Who, your_balance_is, NewBalance}end,{reply, Reply, Tab};%% 用户取钱 handle_call({remove, Who, X}, _From, Tab) ->%% 首先查询用户是否存在%% 存在则根据取款与存款的大小关系分别处理Reply = case ets:lookup(Tab, Who) of[] ->not_a_customer;[{Who, Balance}] when X =< Balance ->NewBalance = Balance - X,ets:insert(Tab, {Who, NewBalance}),{thanks, Who, your_balance_is, NewBalance};[{Who, Balance}] ->{sorry, Who, you_only_have, Balance, in_the_bank}end,{reply, Reply, Tab};%% 停止服务 handle_call(stop, _From, Tab) ->{stop, normal, stopped, Tab}.%% 其它必须实现的回调函数 handle_cast(_Msg, State) ->{noreply, State}. handle_info(_Info, State) ->{noreply, State}. terminate(_Reason, _State) ->ok. code_change(_OldVsn, State, Extra) ->{ok, State}.
运行结果:
1> my_bank:start(). {ok,<0.70.0>} 2> my_bank:deposit("joe", 10). not_a_customer 3> my_bank:new_account("joe"). {welcome,"joe"} 4> my_bank:deposit("joe", 10). {thanks,"joe",your_balance_is,10} 5> my_bank:deposit("joe", 30). {thanks,"joe",your_balance_is,40} 6> my_bank:withdraw("joe", 15). {thanks,"joe",your_balance_is,25} 7> my_bank:withdraw("joe", 45). {sorry,"joe",you_only_have,25,in_the_bank} 8> my_bank:stop(). stopped
16.3 gen_server回调的结构
16.3.1 启动服务器程序时发生了什么
%% 通过start_link启动 %% 创建名为Name的通用服务器程序 %% 回调模块为Mod %% Opts控制服务器程序的行为 %% 调用Mod:init(InitArgs)启动服务器程序 gen_server:start_link(Name, Mod, InitArgs, Opts)
16.3.2 调用服务器程序时发生了什么
%% 通过call调用服务端程序 %% 最终会调用回调模块中的handle_call/3函数 gen_server:call(Name, Request)%% Request 请求信息 %% From 发起调用的客户端进程ID %% State 客户端当前状态 Mod:handle_call(Request, From, State)
16.3.3 调用和通知
%% 通过cast实现通知 %% 最终会调用回调模块中的handle_cast/2函数 gen_server:cast(Name, Name)%% Msg 发送的消息 %% State 状态 Mod:handle_cast(Msg, State)
16.3.4 发送给服务器的原生消息
%% 接收其它进程或系统发送的消息 Mod:handle_info(Info, State)
16.3.5 Hasta La Vista, Baby
%% 这个标题, Joe真有点意思:) Mod:terminate(Reason, NewState)
16.3.6 热代码替换
code_change(OldVsn, State, Extra)
《Erlang程序设计》第十六章 OTP概述相关推荐
- Linux(b站视频兄弟连)自学笔记第十六章——备份与恢复
Linux(b站视频兄弟连)自学笔记第十六章--备份与恢复 概述 dump和restore命令 概述 dump和restore命令
- C++程序设计原理与实践 习题答案 第二十六章 第26章习题答案
第二十六章:测试 习题答案 本章的BinarySearch Binary_Search.h 26.2 26.2 测试集 26.3 26.4 26.5 26.8 and 26.9 26.8 测试集 26 ...
- C++程序设计原理与实践 习题答案 第十六章 第16章习题答案
第十六章:算法和映射 习题答案 16.3 and 16.4, count, count_if 16.5 16.6 16.7 Binary Search 16.8 word frequency 16.9 ...
- c语言压缩文本文件北京理工大学,北京理工大学C语言程序设计第十二章文件.ppt...
北京理工大学C语言程序设计第十二章文件 2000年1月25日 北京理工大学 / 第十二章 文件 第一节 文件概述 第二节 文件的处理 第三节 文件的顺序读写操作 第四节 文件的随机读写操作 第五节 文 ...
- 【正点原子FPGA连载】第三十六章 基于OV5640的PL以太网视频传输实验-摘自【正点原子】领航者ZYNQ之FPGA开发指南_V2.0
1)实验平台:正点原子领航者ZYNQ开发板 2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761 3)全套实验源码+手册+视频下 ...
- 【正点原子FPGA连载】第十六章Petalinux设计流程实战摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Linux开发指南
1)实验平台:正点原子MPSoC开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670 3)全套实验源码+手册+视频下载地址: h ...
- 【正点原子STM32连载】第四十六章 FATFS实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...
- 读书笔记:汇编语言 第三版 王爽 清华出版社 章十六 章十七 章十八
第十六章 直接定址表16.1 描述了单位长度的标号地址标号,表征了位置的偏移地址label:数据标号,表征了一段内存空间的物理地址和长度,增强型地址标号段地址,数据标号所在段的关联段寄存器,assum ...
- 【正点原子MP157连载】第十六章 基本定时器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南
1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...
- 【正点原子MP157连载】 第十六章 UART串口通信实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7
1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...
最新文章
- 如何开发一个属于自己的小程序
- 鸟哥linux私房菜第6章笔记
- ue4加载本地版本_【虚幻4】创建本地数据库
- 【Java】SAX解析characters 错误截取问题的解决
- 作者:姚登举(1980-),男,哈尔滨理工大学副教授。
- 对WITH和from(select ...)的一点比较
- UIFont各种字体
- 企业内部网路怎么防止网络出现环路?
- SDOI2015 寻宝游戏
- android 从app跳转到微信小程序和微信没有设置浮动权限 打不开小程序
- 当我们想要用LinkedIn领英开发客户时,如何设计填写职位头衔?
- 推荐花椒直播服务端的 4 个开源项目
- 读书笔记:《企业IT架构转型之道》
- DOS命令--ASSOC的学习
- 关系运算符lt, gt全拼
- Zookeeper安装与可视化客户端详细使用教程
- Thunderbird学校邮箱登录问题
- JavaScript零基础入门 3:javascript运算符有哪些
- ultraedit html 颜色,UltraEdit主题颜色
- 图像dpi像素调整方法
热门文章
- 免费企业邮箱怎么注册申请
- 构造Dominator Tree以及Dominator Frontier
- 【一文读懂】python 中的 numpy.reshape(a, newshape, order=‘C‘) 详细说明及实例讲解
- 计算机毕业设计Java车辆调度管理系统(源码+系统+mysql数据库+lw文档
- 正则表达式中问号(?)的用法详解
- SpringSecurity实现自定义登录界面
- 读博与怠工(在北航硕博连读)——转载
- win7浏览器主页修改不过来_win7无法修改ie浏览器主页的解决方法
- 看了下华为工资,我不加班了
- mysql mybatis批量删除,Mybatis批量删除多表