• 1. 第八章 CORBA服务
  • 2. 对象查找与通信查找 基本机制 命名服务 交易对象服务 通信 机制 事件服务 通知服务 消息服务
  • 3. 对象查找机制对象目录:存储对象及其关联的数据 命名服务:存储对象引用与一个名字的关联 交易对象服务:存储对象引用与一系列相关属性之间的关联客户程序对象实现1 发布2 查找3 使用name1name2nameNOOO
  • 4. 对象引用的查找预定义系统对象 用户自定义类型 工厂对象 扩展机制 命名服务
  • 5. 对象引用的查找——预定义系统对象Resolve_initial_references:得到提供所需服务的对象(预定义系统对象)的对象引用 用于表示不同服务的字符串(硬编码名字):RootPOA, POACurrent, InterfaceRepository, NameService, TradingService, SecurityCurrent, TransactionCurrent, DynAnyFactory Narrow():将获得的通用对象引用窄化为具体对象类型
  • 6. 对象引用的查找——用户自定义类型Object_to_string()与string_to_object 服务器:Object_to_string将对象引用字符串,保存在一个客户可以访问的存储区域 客户:访问存储区域,获得字符串化的对象引用,利用string_to_object将其反字符串化,得到对象引用 适合获取初始对象引用
  • 7. 对象引用的查找——工厂对象工厂对象:获取其他对象引用的对象 public synchronized Bank.Account open(String name) { // 在账户清单中查找指定名字的账户 Bank.Account account = (Bank.Account) accountList.get(name); // 如果不存在则新创建一个 if (account == null) { // 随机虚构账户的初始余额,金额在0至1000之间 Random random = new Random(); float balance = Math.abs(random.nextInt()) % 100000 / 100f; // 按指定余额创建账户的伺服对象 AccountImpl accountServant = new AccountImpl(balance); try { // 用默认的POA激活伺服对象,这里默认的POA就是根POA org.omg.CORBA.Object obj = _default_POA().servant_to_reference(accountServant); // 将对象引用收窄为账户类型 account = Bank.AccountHelper.narrow(obj); } catch(Exception exc) { exc.printStackTrace(); } // 将账户保存到账户清单中 accountList.put(name, account); // 在服务端控制台打印已创建新账户的提示信息 System.out.println("新开账户:" + name); } // 返回找到的账户或新开设的账户 return account; }
  • 8. 对象引用的查找——扩展机制Visibroker,Orbix:bind() // 利用POA全名与对象标识"BankManager"查找账户管理员 Bank.AccountManager manager = Bank.AccountManagerHelper.bind( orb, "/BankPOA", "BankManager".getBytes());
  • 9. 对象引用的查找——命名服务等命名服务:根据给定名字提供所需的对象引用 交易服务:根据服务类别寻找对象引用
  • 10. 命名服务支持一个具有含义的名字绑定到一个CORBA对象 帮助类的bind: 用一个全局的对象标识查找并绑定一个对象引用 不同对象标识间不存在任何关联 命名服务:将与对象应用关联的名字组织为一种层次结构的名字空间
  • 11. 6.2 命名服务定义:给对象实例提供一个名称,便于用户通过其来获取对象实例 作用:是ORB上对象找到其他对象的基本机制 名字联编:名字-对象(标记)关联 可创建命名分层结构 来自不同域的名字语言环境可以一起使用,为对象创建联合命名服务
  • 12. 6.2.1 层次结构节点都是对象,分为两类 环境对象:命名环境(naming context),空心节点,可以是节点和叶子 应用对象:应用程序的对象,只能是叶子 弧:表示对象引用,且用该对象在context中出现的名字标识 可根据对象名字通过遍历的方式找到目的对象的对象引用aaeebbccddffgg
  • 13. 名字服务:Naming Context:Naming Context:Naming Context:StockWatch:StockWatch:PManager“StockWatch”“PortfolioManager”“NASDAQ”“NYSE”“MainAdmin”
  • 14. 6.2.2 对象的命名内容 Corba对象的句柄定义成如URL(统一资源定位)的形式,从而允许ORB来调用基于Corba的服务或者远程ORB上的对象实例 上下文对象:任何一个厂商的ORB都可以通过配置客户端的ORB来初始化根命名服务的上下文对象(NamingContext) 对于Corba对象的复合命名。定义一个标准的语法规则。这样服务器端和客户端就可以通过相同的格式来进行读写消息。
  • 15. 6.2.2.1 Corba对象的统一资源定位(URL):Corbaloc 通常的做法是把一个Corba的服务器放在一台有指定域名地址和端口的机器上。Corba的服务器要为许多的Corba对象服务。一般我们需要在对象实例 的URL地址后加上特定的对象健值(Object Key ID)或对象名字,这些对象的名字或健值就指明了我们所需要的特定的服务。如下所示:Corbaloc:yourdomain.com/NameService
  • 16. 6.2.2.2 Corbaloc URL的完整格式 IIOP:1.2@yourdomain.com:2809/pub/nameservice
  • 17. 6.2.3 名字服务模型名字服 务器客户 服务器 OA对象1、对象注册2、名字解析3、远程方法引发
  • 18. 6.2.4 名字服务编程要素module CosNaming{…….}// struct NameComponent { string id; //用户的字符串标识符 string kind;//扩展名//描述信息,如编码,版本 }; typedef sequence Name;
  • 19. 6.2.4.1 NameComponet 对象的名字由一系列NameComponet顺序串接而成 只有一个NameComponet的名字成为简单名 多个——复合名 复合名 最后一个是简单名,表示应用对象 其他都是环境名 复合名由若干环境名和一个简单名串接而成
  • 20. enum BindingType {nobject, ncontext}; struct Binding { Name binding_name; BindingType binding_type; }; typedef sequence BindingList;
  • 21. interface NamingContext { void bind(in Name n, in Object obj); void rebind(in Name n, in Object obj); void bind_context(in Name n, in NamingContext nc); void rebind_context(in Name n, in NamingContext nc); Object resolve (in Name n); void unbind(in Name n); NamingContext new_context();//命名环境的创建 NamingContext bind_new_context(in Name n); void destroy( );//命名环境的删除 void list (in unsigned long how_many, //命名环境的列表 out BindingList bl, out BindingIterator bi); };
  • 22. 6.2.4.2 绑定Bind()在当前命名环境中命名一个对象 如对象ee与名为aa的环境对象绑定 Bind_context():在当前命名环境中命名一个命名环境 在当前名为dd的环境对象中绑定另一个名为gg的环境对象aaeebbccddffgghh
  • 23. 6.2.4.3解析从一个名字得到一个与之绑定的对象的操作 复合名所定义的就是一条解析各个环境名的路径,直至到达简单名 Object solve(in Name n) 返回的对象可能是一个被绑定的应用对象或命名环境,要将其narrow到合适对象类型
  • 24. 6.2.4.4 命名环境的列表void list (in unsigned long how_many, out BindingList bl, out BindingIterator bi); 列出当前命名环境中下一级所有绑定的名字
  • 25. 6.2.4.5 迭代器 interface BindingIterator { boolean next_one(out Binding b); boolean next_n(in unsigned long how_many, out BindingList bl); void destroy(); };
  • 26. 6.2.5 名字服务编程启动名字服务器 实例化服务对象 服务对象在名字服务器上注册 客户在名字服务器上按名查找对象 客户引发对象的方法
  • 27. 6.3.1 事件服务体系结构suppliersupplierConsumerConsumerConsumerEvent ChannelPULLPULLPUSHPUSHPUSH
  • 28. 提供者:负责生产事件数据(发布者) 消费者:接收和处理事件数据(订阅者) 事件通道:中转媒介,可实现多个消费者和提供者的异步通信 两种事件数据传送模型: 推push模型:事件的提供者起带头作用,发起事件的传送; 拉pull模型:事件的消费者起带头作用,向提供者请求事件数据。
  • 29. 6.3.2 事件服务push模型事件通道推消费者推消费者推提供者推提供者Push()Push()通知人
  • 30. 事件服务pull模型事件通道拉消费者拉消费者拉提供者拉提供者Pull()Pull()获取者
  • 31. 事件服务混合push/pull模型事件通道拉消费者拉消费者推提供者推提供者Push()Pull()队列
  • 32. 事件服务混合pull/push模型事件通道推消费者推消费者拉提供者拉提供者Pull()Push()智能代理
  • 33. 6.3.3 事件服务结构事件通道消费者提供者提供者代理消费者代理
  • 34. 事件服务push模型事件通道推消费者 #1推消费者 #2推消费者 #3磁盘满磁盘满磁盘满推提供代理 #1推提供代理 #2推提供代理 #3磁盘满事件提供者磁盘满推消费代理
  • 35. 事件服务pull模型事件通道拉模式代理 事件消费者 拉模式事 件消费者拉模式代理 事件提供者事件提供者正常服务器正常吗?连接服务器:正常正常连接
  • 36. 6.3.4 事件服务Event channelSupplierAdminProxyPushConsumerPushSupplierProxyPullConsumerPullSupplierConsumerAdminProxyPushSupplierPushConsumerProxyPullSupplierPullConsumer
  • 37. 6.3.5 接口——push模型接口module CosEventComm{……} interface PushConsumer { void push (in any data); void disconnect_push_consumer(); };   interface PushSupplier { void disconnect_push_supplier(); };
  • 38. pull模型接口module CosEventComm{……} interface PullSupplier { any pull (); any try_pull (out boolean has_event); void disconnect_pull_supplier(); };   interface PullConsumer { void disconnect_pull_consumer(); };
  • 39. 事件通道接口 module CosEventChannelAdmin {……} interface EventChannel { ConsumerAdmin for_consumers(); SupplierAdmin for_suppliers(); void destroy(); }; interface EventChannelFactory { EventChannel create_eventchannel (); }; 
  • 40. 事件服务 interface SupplierAdmin { ProxyPushConsumer obtain_push_consumer(); ProxyPullConsumer obtain_pull_consumer(); }; interface ConsumerAdmin { ProxyPushSupplier obtain_push_supplier(); ProxyPullSupplier obtain_pull_supplier(); };
  • 41. 事件服务  interface ProxyPushSupplier: CosEventComm::PushSupplier { void connect_push_consumer( in PushConsumer push_consumer); }; interface ProxyPushConsumer: CosEventComm:: PushConsumer { void connect_push_supplier( in PushSupplier push_supplier); }; 
  • 42. 事件服务 interface ProxyPullSupplier: CosEventComm:: PullSupplier { void connect_pull_consumer( in PullConsumer pull_consumer); }; interface ProxyPullConsumer: CosEventComm:: PullConsumer { void connect_pull_supplier( in PullSupplier pull_supplier); };
  • 43. 6.3.6 模型使用步骤 在事件通道一侧: 从名字服务中获得事件通道工厂 使用事件通道工厂创建事件通道 将事件通道注册到名字服务中
  • 44. 模型使用步骤 在提供者一侧: 从名字服务中获得事件通道 创建一个供应者管理器 从事件通道中为对端消费者在本地创建一个消费者代理 将提供者与消费者代理连接,即向通道注册 创建一个消息,并将消息推给消费者
  • 45. 模型使用步骤 在消费者一侧: 从名字服务中获得事件通道 创建一个消费者管理器 从事件通道中获取一个提供者的代理 将消费者连接到提供者代理,表示向同一通道注册
  • 46. 推模型
  • 47.  1.对于一个推模型的推提供者来讲,基本的事件序列如下:  (1)绑定到ORB和事件信道上:   org.omg.CORBA.ORB orb= org.omg.CORBA.ORB.init(agrs,null);   EventChannel eventChannel= EventChannelHelper.bin(orb);   (2) 从事件信道中获取一个推消费者代理:   SupplierAdmin admin= eventChannel.for_suppliers(); // 对应Push事件脚本的第一步   ProxyPushConsumer pushConsumer = admin .obtain_push_consumer();   // 对应图中Push事件脚本的第二步   (3) 创建一个推提供者:   MySupplier pushSupplier=new MySupplier();   (4)将提供者连接到事件信道:   pushConsumer.connect_push_supplier(pushSupplier);//对应图中Push事件脚本的第三步  (5)创建一个CORBA消息:  HelloImplementation msg=new HelloImplementation()   (6)利用CORBA消息创建一个Any事件对象:    Org.omg.CORBA.Any pushMessage=orb.create_any();   PushMessage.insert_Object(msg);   (7) 将消息推给消费者:   pushConsumer.push(pushMessage);
  • 48.  2. 对于一个推模型的推消费者来讲,基本的事件序列如下: (1)绑定到ORB和事件信道上:   org.omg.CORBA.ORB orb= org.omg.CORBA.ORB.init(agrs, null);   EventChannel eventChannel= EventChannelHelper.bin(orb);   (2) 从事件信道中获取一个推提供者代理:   ConsumerAdmin admin= eventChannel.for_consumers();// 对应图中Push事件脚本的第四步   ProxyPushSupplier pushSupplier = admin .obtain_push_supplier();   // 对应图中Push事件脚本的第五步   (3) 创建一个推消费者:   MyConsumer pushConsumer=new MyConsumer ();   (4) 将消费者连接到事件信道:   pushSupplier.connect_push_consumer(pushConsumer); // 对应图中Push事件脚本的第六步   (5) 在一个 push()实现中被接收:   public void push(Any pushMsg){   // 对应图中Push事件脚本的第七步   org.omg.CORBA.Object object=pushMsg.extract_Object();   Hello msg=HelloHeper.narrow(object);   }
  • 49. 6.4 交易器服务概述 基本概念 互连机制
  • 50. 6.4.1 概述 功能:类似黄页,可向其他对象发布它们所能提供的服务或查找所需服务 Export:向交易器给出服务的描述以及服务所在界面的位置(服务输出) Import:对象要求交易器提供符合一定条件的服务(服务输入) import与Export实现了服务的动态查找和延后绑定 如果需求超过分区能力,则 直接访问:用户直接与该服务区的交易器交互 间接访问:用户仅与一个交易器交互,并且此交易器再与其他交易器交互TraderClientServer1 Export2 Import3 服务交互
  • 51. 多样性和伸缩性包含大量服务——易获得性 包含少量服务——性能 包含 包含所有输出服务的位置 包含其他服务区信息
  • 52. 连接的交易器可以跨越域边界 交易服务是一种联邦系统,可跨越多个域
  • 53. 策略,约束,优先选择通过策略,约束,优先选择可以使交易器裁减其搜索 策略:告诉交易器如何完成搜索。决定交易器的行为 约束:指明一种搜索标准 优先选择:指明匹配的服务应被返回的优先顺序
  • 54. 基本概念Exporter(输出方):服务提供者或代表其他服务提供者发布服务 Importer(输入方):通过交易器查找符合条件的服务。是潜在的服务客户或代表其他客户输入所要求的服务 服务类型:描述一个服务所需信息 界面类型: 零个或多个命名的属性类型
  • 55. 服务类型用于描述一个服务,包含提供这个服务的接口类型以及一系列用于描述该服务的特征类型 每个特征类型都有一个<名字,类型,模式>的三元组与之联系 特征模式:用于表明特征类型是否是必需,只读
  • 56. 服务类型模型服务类型之间可以继承自多个服务类型,被继承的服务类型称为基服务类型 接口类型必须同基类型的接口类型相同或继承而来 Mandatory:输出服务供应时服务类型实例必须为该类型提供适当的值 Readonly:输出服务供应时如该属性已被赋值,则该属性不能被后面的Register:modify()操作改变Service [:[,*]] {interface ; [[mandatory][readonly] property;]* };
  • 57. 广告类型名:建筑物广告 接口:Building 特征: 建筑物名称 字符串 必需且只读 地址 字符串 必须且只读 开发商 字符串 可选且只读 承建单位 字符串 可选且只读 工程等级 整数 可选且只读 每平米价格 整数 必需且可修改 建议用途 字符串 可选且可修改 广告类型:住宅建筑物广告 接口:DwellingHouse 特征: 售房许可证号 整数 必需只读 最小户型面积 整数 可选只读 最大户型面积 整数 可选只读ServiceTypeNameInterfacePROM_MANDATORY_READONLYPROM_READONLYPROM_NORMALServiceTypeNameDwellingHouse继承自BuildingPROM_MANDATORY_READONLYPROM_READONLY
  • 58. 服务供应(service offer)服务类型的实例化 按照服务类型给出自己的特征取值和满足这些特征取值的接口实例 必须提供 服务类型名 所有特征模式为必需的特征的取值 接口类型的引用
  • 59. 广告类型名:建筑物广告 接口:Building 特征: 建筑物名称 字符串 必需且只读 地址 字符串 必须且只读 开发商 字符串 可选且只读 承建单位 字符串 可选且只读 工程等级 整数 可选且只读 每平米价格 整数 必需且可修改 建议用途 字符串 可选且可修改 实例:xingFuBuilding 特征: 建筑物名称 “幸福大厦” 地址 “学院路10号” 开发商 “阳光开发集团” 承建单位 “市一建公司” 工程等级 1 每平米价格 3000 建议用途 “办公” 一个服务供应被提交给交易者后,交易者会向出口者返回一个通告标识OfferId供出口者检验,撤回或修改这个服务通告,OfferId在一个交易者中可以唯一标识一个服务供应
  • 60. 服务供应的选择交易器使用策略来确定需要查找的服务供应集S1 在S1上通过服务类型和约束条件来确定满足服务类型和约束条件的集合S2 在把返回service offer给输入方前根据优先选择条件来确定最后的service offer
  • 61. 策略(Policies)用值名对表示 两类 限制搜索范围 确定操作功能潜在服 务供应被考虑的 服务供应已匹配的 服务供应已排序的 服务供应被返回的 服务供应搜索策略匹配策略返回策略聚集匹配返回N1N2N3N4N5满足服务类型和约束条件具一致性服务类型
  • 62. 优先选择条件用于决定返回匹配的服务供应的顺序 优先选择串 Max: 降序返回 Min:升序 With:约束表达式(真在假前) Random: 随机 First:发现顺序
  • 63. 交易服务的描述提供三个IDL模块:CosTradingRepos, CosTrading, CosTradingDynamic CosTradingRepos:定义,检验和删除服务类型的功能 ServiceTypeRepository:存放服务类型描述,7种操作,主要使用add_type, list_type, descirbe_type(获得细节信息) CosTrading:创建,查找服务供应,创建代理服务供应,修改交易联盟,配置交易功能 Loopup:查询所需服务 Register:登记供应者发布的服务,主要使用export(公告服务), describe(获得所需服务供应的详细信息)
  • 64. 交易流程1Add_type2 export3 describe4 List_type5 Describe_type6 query7 调用服务CosTradingRepos: ServiceTypeRepositoryCosTrading:RegistorCosTrading:Lookup交易者管理环境交易者核心环境Importerexporter
  • 65. 1 登记新服务类型。Exporter调用ServiceTypeRepository接口中的add_type操作,将准备发布的服务类型存放到服务类型库中 2 发布新服务。Exporter调用Register中的export操作,将新的服务以规定的服务供应格式进行注册,供应内容包括服务类型名,申请登记服务的对象引用以及已赋值的服务特性 返回:唯一的服务供应标识
  • 66. 3 检查已登记的服务供应。Exporter给出指定的通告标识(OfferId),调用Register的describe操作,可得到与该标识对应的服务通告 4 请求服务类型列表。Importer调用list_type,得到服务类型清单 5 请求服务类型的描述。Importer从服务类型列表清单中选出所需的服务类型,用相应的服务类型名调用describe_type方法,获取以下信息:接口名字,特征类型,所继承的基服务类型名
  • 67. 6 查询指定服务类型的全部实例。Importer根据给定的某种服务类型以及前面的查询参数,调用lookup接口中的query操作,得到满足查询条件的,对应于该服务类型的全部服务通告,使进口者获得所有能提供这些服务的对象引用 7 调用服务。Importer给出服务的对象引用,调用对应服务,完成一个交易周期
  • 68. 互连机制连接遍历控制 联邦查询实例 代理服务供应
  • 69. 连接遍历控制交易器连接的弹性容许任意连接交易器 问题 每个交易器可能不止一次被访问,因为一个交易器可以出现在不同路径上。源交易器为每个查询操作产生一个RequestID 查询始发者生成Requestid,并以字节流方式放入request_id策略中,一个交易者可记下自己处理过的Requestid,若发现有相同requestid,表明已处理过,可不处理 可能产生循环。 hop_count用于限制查询被传递的深度
  • 70. 联邦查询实例一个交易器可以与其他交易器连接,使其他交易者的信息对自己同样有用 一个register可提供resolve()操作来获得其他的register,其输入可以是直接与该register连接的交易者的名字,也可是间接与该register连接的交易者的名字 间接方式下,要将所有中间经过的交易器的名字都串接,形成一个序列
  • 71. Trader ATrader BTrader DTrader CTrader EA.resolve(B,D)A.resolve(B,D)
  • 72. Query.hop_count=4Def_follow_ploicy=alwaysMax_hop_count=5Query.hop_count=3Max_hop_count=1Def_follow_ploicy=alwaysQuery.hop_count=0Def_follow_ploicy=alwaysMax_hop_count=4Service offerLinkTrader AttributeT1T2T3T4Request_id
  • 73. 1 一个查询请求以输入方hop_count=4的值被调用,T1的范围搜索限制策略中的max_hop_count=5,所以最终用于搜索的hop_count=4 2 假定在T1中未找到一个合适的匹配而跟随策略为always,则T1将请求传递给T3,输入方的hop_count--=3,作为T3的查询输入值。 T3的本地max_hop_count=1,且产生request_id以避免对同一交易器的重复搜索。 这样用于T3查询的hop_count=1且T3_request_id被保存
  • 74. 3 假定T3未找到一个合适的匹配而跟随策略为always,则T4的策略输入参数:hop_count=0, request_id=T3_request_id 4 假定在T4中未找到一个合适匹配,此时即使T4的max_hop_count=4,搜索也不会继续被传递。搜索失败将返回给T3,T1,直至T1的使用者 ×查询操作是否在其余交易器上被传递取决于连接跟随策略,若为always,查询将到达所有满足hop_count策略的交易器
  • 75. 6.7 消息中间件及CORBA消息服务概述 MQSeries结构 MQSeries主要特征 基于CORBA的消息中间件
  • 76. 6.7.1 概述三种消息中间件 RPC CPIC(会话编程,如TCP/IP的Socket) MQI(MQSeries类型的消息队列中间件) 三种工作模式 1点对点 2消息队列:主导 3发布/订阅:非正式标准 产品 IBM MQSeries:异步消息处理技术确保消息可靠传送 JMS(Java Message Service):提供1,3模式
  • 77. 6.7.1.1 JMS可提供基础TCP/IP,HTTP,SSL,E-Mail或其他通讯协议 消息可一直保存在一个代理管理的数据池中,直到所有订阅者收到它
  • 78. 6.7.2 MQSeries结构消息通道协议 消息路由 处理过程 触发机制 消息流动
  • 79. 6.7.2.1 消息通道协议(MCP)负责将消息投送给不同系统的各种底层传输层协议 与存储-传递方法结合。 传输链路不存在时,可存放消息,恢复后传输 支持的传输协议:LU6.2 , DECnet,TCP/IP
  • 80. 6.7.2.2 消息路由头记录:队列名@队列管理器 算法: 队列管理器名标识目的队列所在的MQSeries系统 若队列管理器名未指定,则为本地队列管理器
  • 81. MQSeries系统队列管理器(M1)本地对应队列Q1MQI放入M1的传输队列(Q1,Q2…)队列管理器是否是本地?Y消息:Q1@M1Q1队列应用程序应用程序本地系统远程系统本地队列管理器(M1)
  • 82. 6.7.2.3 触发机制定义:使应用程序在服务到达时被激活的机制触发队列Q1应用程序队列管理器M1触发监控器触发事件126354启动队列TQ
  • 83. 1 一条消息(Q1@M1)到达被触发的应用队列Q1 2 队列管理器M1参考环境信息决定这条消息是否构成一次触发事件 3 队列管理器M1创建一条触发消息T,并把触发消息发送到一个启动队列TQ
  • 84. 4 触发监控器从启动队列中读出触发消息 5 触发控制器发出一条激活与触发队列相关联的应用程序的命令 6 被启动应用程序进行相应操作,应用程序从触发队列中取走消息
  • 85. 6.7.2.3.1 三种触发类型第一条消息触发:一条消息到达一个空队列时产生;适于一旦被启动就处理完队列上所有消息 每一条消息触发:任意一条消息到达队列都产生;适于需要立即处理;增加开销 多条消息触发:队列上有特定数目消息时产生;适于监控特定队列上消息的数目,并在数目达到某临界值时对用户给出提示
  • 86. 6.7.2.4 消息流动通过消息通道代理MCA完成MCA队列管理器(M1)放入M1的传输队列(Q1,Q2…)Q1队列MCA
  • 87. 6.7.4 基于CORBA的消息中间件核心:MOB,通过MOB提供的接口MOBI来发布和订阅消息 功能:对消息管理,负责发布,订阅 消息分类: 临时性:处理后立刻删除 持久:存储在文件中 介于两者之间:驻留于内存,关闭或显式删除
  • 88. 发布/订阅模式松耦合 空间 发布者和订阅者不必相互知道 时间非耦合 发布者和订阅者不必同时在线 数据流非耦合 发布/订阅异步