ICE中间件技术详细教程


ICE 中间件 INTERNET COMMUNICATION ENGINE MIDDLEWARE ICE 中间件 仅供内部使用 第 1 页 2014 年 7 月 2 日 索引 1 认识 ICE ..................................................................................................................... 4 1.1 ICE 概述 .................................................................................................................................... 4 1.2 ICE 结构 .................................................................................................................................... 4 1.2.1 客户端和服务器 ................................................................................................................... 5 1.2.2 Ice 核心 .................................................................................................................................. 5 1.2.3 Ice API ................................................................................................................................... 6 1.2.4 对象适配器 ........................................................................................................................... 6 1.2.5 Ice 代理 .................................................................................................................................. 6 1.2.6 Ice 骨架(skeleton) ............................................................................................................ 7 1.3 Slice 语言 .................................................................................................................................. 7 1.3.1 Slice 合约功能....................................................................................................................... 7 1.3.2 Slice 映射机制 ...................................................................................................................... 8 1.4 Ice 协议 ..................................................................................................................................... 8 1.4.1 Ice 协议的组成 ...................................................................................................................... 8 1.4.2 Ice 协议的优势 ...................................................................................................................... 9 1.4.3 数据编码规则 ....................................................................................................................... 9 1.4.4 协议状态机制 ....................................................................................................................... 9 1.5 Ice 服务 ................................................................................................................................... 10 1.5.1 IceFreeze .............................................................................................................................. 10 1.5.2 IceGrid ................................................................................................................................. 10 1.5.3 IceBox .................................................................................................................................. 11 1.5.4 IceStorm ............................................................................................................................... 11 1.5.5 IcePatch2 .............................................................................................................................. 12 1.5.6 IceGlacier2 ........................................................................................................................... 12 1.6 Ice 架构的优势 ........................................................................................................................ 12 2 Slice 语言快览 .......................................................................................................... 15 2.1 简介 ......................................................................................................................................... 15 2.2 编译 ......................................................................................................................................... 15 2.2.1 客户端和服务器端使用相同的开发环境 ......................................................................... 16 2.2.2 客户端和服务器端使用不同的开发环境 ......................................................................... 17 2.3 Slice 源文件............................................................................................................................. 17 2.4 词法规则 ................................................................................................................................. 18 2.5 规范速览 ................................................................................................................................. 20 2.5.1 模块 ..................................................................................................................................... 20 2.5.2 基本数据类型 ..................................................................................................................... 21 2.5.3 枚举 ..................................................................................................................................... 21 2.5.4 结构 ..................................................................................................................................... 21 2.5.5 序列 ..................................................................................................................................... 22 2.5.6 词典 ..................................................................................................................................... 22 2.5.7 接口 ..................................................................................................................................... 22 2.5.8 参数 ..................................................................................................................................... 23 ICE 中间件 仅供内部使用 第 2 页 2014 年 7 月 2 日 2.5.9 Idempotent(幂等操作) ........................................................................................................ 23 2.5.10 nonmutating 操作 ................................................................................................................ 23 2.5.11 用户异常 ............................................................................................................................. 23 2.5.12 代理 ..................................................................................................................................... 24 2.5.13 接口继承 ............................................................................................................................. 25 2.5.14 类 ......................................................................................................................................... 26 2.5.15 提前声明 ............................................................................................................................. 27 2.5.16 本地类型 ............................................................................................................................. 27 2.5.17 名字与作用域 ..................................................................................................................... 27 2.5.18 元数据指令 ......................................................................................................................... 27 2.5.19 Slice 检查和 ........................................................................................................................ 28 3 ICE 应用—开发初探 ............................................................................................... 29 3.1 Hello Wrold 应用 .................................................................................................................... 29 3.1.1 实现目的 ............................................................................................................................. 29 3.1.2 C++实现 .............................................................................................................................. 29 3.1.2.1 定义 Slice 文件............................................................................................................ 29 3.1.2.2 编译 Slice 文件............................................................................................................ 30 3.1.2.3 服务器端 ..................................................................................................................... 30 3.1.2.4 客户端 ......................................................................................................................... 32 3.1.2.5 运行 ............................................................................................................................. 33 3.1.3 Java 实现 ............................................................................................................................. 34 3.1.3.1 定义 Slice 文件............................................................................................................ 34 3.1.3.2 编译 Slice 文件............................................................................................................ 34 3.1.3.3 服务器端 ..................................................................................................................... 35 3.1.3.3.1 接口实现 ............................................................................................................... 35 3.1.3.3.2 服务器主程序 ....................................................................................................... 35 3.1.3.4 客户端 ......................................................................................................................... 36 3.1.3.5 运行 ............................................................................................................................. 38 3.2 总结 ......................................................................................................................................... 38 4 Ice 应用—开发基础 ................................................................................................. 39 4.1 本章目的 ................................................................................................................................. 39 4.2 客户端 Slice 到 C++的映射 ................................................................................................... 39 4.2.1 模块映射 ............................................................................................................................. 39 4.2.2 类型映射 ............................................................................................................................. 39 4.2.3 异常映射 ............................................................................................................................. 40 4.2.4 接口映射 ............................................................................................................................. 41 4.2.5 操作映射 ............................................................................................................................. 43 4.2.6 异常处理 ............................................................................................................................. 43 4.2.7 类映射 ................................................................................................................................. 43 4.3 服务器端 Slice 到 C++的映射 ............................................................................................... 45 4.3.1 服务器端程序流程 ............................................................................................................. 46 4.3.2 Ice::Application 类............................................................................................................... 46 4.3.3 Ice::Service 类 ..................................................................................................................... 49 4.3.4 接口映射 ............................................................................................................................. 52 ICE 中间件 仅供内部使用 第 3 页 2014 年 7 月 2 日 4.3.5 参数传递 ............................................................................................................................. 52 4.3.6 异常 ..................................................................................................................................... 53 4.3.7 激活服务者 ......................................................................................................................... 53 4.4 简单文件系统的设计和实现 ................................................................................................. 54 4.4.1 系统需求 ............................................................................................................................. 54 4.4.2 定义 Slice 文件.................................................................................................................... 55 4.4.3 服务器端 ............................................................................................................................. 55 4.4.3.1 主程序 ......................................................................................................................... 56 4.4.3.2 服务者类定义 ............................................................................................................. 58 4.4.3.3 服务者类实现 ............................................................................................................. 60 4.4.4 客户端 ................................................................................................................................. 63 4.4.5 总结 ..................................................................................................................................... 66 5 Ice 应用—高级进阶 ................................................................................................. 67 5.1 Ice 属性和配置 ........................................................................................................................ 67 5.2 C++线程和同步 ...................................................................................................................... 67 5.3 Ice 流接口 ................................................................................................................................ 67 5.4 异步程序设计(AMI 和 AMD) .......................................................................................... 67 5.5 动态调用和分派程序设计 ..................................................................................................... 67 5.6 其它(待补充) ..................................................................................................................... 67 6 Ice 服务详解及应用 ................................................................................................. 68 6.1 IceGrid ..................................................................................................................................... 68 6.2 Freeze 与 FreezeScript ............................................................................................................. 68 6.3 IceSSL ...................................................................................................................................... 68 6.4 Glacier2 .................................................................................................................................... 68 6.5 IceBox ...................................................................................................................................... 68 6.6 IceStorm ................................................................................................................................... 68 6.7 IcePatch2 .................................................................................................................................. 68 7 Ice 功能扩充—插件模块 ......................................................................................... 69 8 Ice 在不同平台和语言的开发和运行环境配置 .................................................... 70 ICE 中间件 仅供内部使用 第 4 页 2014 年 7 月 2 日 1 认识 ICE 1.1 ICE 概述 网络通信引擎(Internet Communications Engine, Ice)是由 ZeroC 的分布式系统开发专家实 现的一种高性能、面向对象的中间件平台。它号称标准统一,开源,跨平台,跨语言,分布式, 安全,服务透明,负载均衡,面向对象,性能优越,防火墙穿透,通讯屏蔽。因此相比 CORBA,DCOM,SOAP,J2EE 等的中间件技术,自然是集众多优点于一身,而却没有他们的 缺点。 Ice 提供了完善的分布式系统解决方案,适合所有的异构网络环境:客户端和服务器端可以 用不同的程序语言来实现,可以运行在不同的操作系统和不同的体系结构的机器上,使用不同 的网络通信技术(TCP/UDP,SSL 或通过插件功能扩展协议)。Ice 也提供了客户端和服务器端 的完全分离,客户端不需要知道服务器的实现过程和具体位置。Ice 采用软总线的机制,使得在 任何情况下、采用任何语言开发的软件只要符合接口规范的定义,均能集成到分布式环境中去。 Ice 面向对象,可以将所有应用看作是对象及相关操作的集合,构建在 Ice 之上的分布式系统的 对象的获取只取决于网络的通畅性和获取服务对象特征的准确程度,而与对象的位置以及对象 所处的设备环境无关。 Ice 提供了简单的对象模型和类型系统,精简而强大的运行时 API,简单的语言映射,紧凑 高效并可扩展的协议,丰富的客户端调用和服务器端分派方式,完善的安全解决方案,大量高 效而实用的服务和工具。基于这些,Ice 特别适合对技术和性能要求都很高的分布式系统开发。 由于这些原因,现在 Ice 已经被很多大公司采用,作为安全、伸缩性强的底层通信平台。 1.2 ICE 结构 Ice 是一种面向对象的中间件平台,从根本上说,这意味着 Ice 为构建面向对象的客户/服务 器模式的应用提供了工具、应用程序接口(API)和库支持。Ice 的体系机构如下图所示: ICE 中间件 仅供内部使用 第 5 页 2014 年 7 月 2 日 图 1.1 Ice 体系结构 1.2.1 客户端和服务器 客户端是主动的实体,向服务器端发出服务请求;服务器端是被动的实体,他们提供服 务,响应客户端的请求。这两个角色并不是应用系统的组成部分的严格指称,而是表示在某 个请求从发生到结束期间,应用系统某些部分所承担的角色。通常这样的角色界定是不固定 的,甚至会经常性的发生反转行为。所以,许多客户/服务器常可以被准确的描述为对等系统 (peer-to-peer),客户端和服务器角色只有在执行某个特定操作、在特定的时间能才有绝对 意义。 1.2.2 Ice 核心 Ice 核心包含大量的链接库,是处于核心地位的对象总线,为客户端和服务器的远程通 信提供支持。它主要关心的是网络通信、线程、字节序、其它一些网络细节及相关事务等, 并将应用程序与这些底层事务隔离开。 Ice 核心定义了异构环境下对象透明的发送请求和接收响应的基本机制,是建立对象之 间客户端/服务器模型的核心组件。它使对象可以透明的向其它对象发出请求或者接收其它对 象的响应,这些对象可以位于本地也可以位于远端机器。Ice 核心截获客户端的请求调用, 找到可以实现请求的服务器对象,并负责传送参数、调用相应的方法、返回结果等。客户端 对象并不需要了解和服务器对象的通信、激活或存储服务期对象的机制,也不需要服务器对 ICE 中间件 仅供内部使用 第 6 页 2014 年 7 月 2 日 象位于何处、用何种语言实现、使用什么操作系统或其它不属于对象接口的系统部分。对于 服务器对象,也是如此。 Ice 核心提供一种客户段/服务器的松耦合通信方式,使得开发人员可以把更多的注意力 放在应用逻辑的实现上。 1.2.3 Ice API Ice 应用程序接口(API),提供对 Ice 核心的通用部分(与 Slice 中定义的特定类型无关的 部分)的访问,程序开发人员可以使用 Ice API 做一些 Ice 的管理事务,例如 Ice 运行时的初始 化和结束。客户端和服务器所使用的 Ice API 是一样的,在服务器使用的可能会多一些。 1.2.4 对象适配器 对象适配器是专用于服务器端的 Ice API 的一部分:只有服务器才使用对象适配器。对象服 务器有若干功能,如下所列: 1. 对象适配器把来自客户端的请求映射到服务器端特定对象的特定方法上。 2. 对象适配器会跟踪在内存中的伺服对象,记录其对象标识,从而实现适配请求的功能。 3. 对象适配器可以与一个或多个传输端点关联在一起。如果与某个适配器关联的传输端点 不止一个,就可以通过多种传输机制到达在该适配器中的伺服对象。为了提供不同的服 务质量和性能,一个适配器可以同时关联一个 TCP/IP|端点和一个 UDP 端点。 4. 对象适配器负责创建传给客户端的 Ice 代理。对象适配器知道每个对象的类型、标识以 及传输机制的详细信息。当服务器端应用程序要求创建代理时,对象适配器会在其中嵌 入正确的信息。 1.2.5 Ice 代理 代理代码是由用户定义的 Slice 文件经过编译后生成的。一个客户端要想与一个 Ice 对象建 立联系,必须持有该 Ice 对象的代理。代理是存在于客户端地址空间的园地 Ice 对象的代表。代 理主要有两个功能: ICE 中间件 仅供内部使用 第 7 页 2014 年 7 月 2 日 1. 为客户提供了一个向下(down-call)调用的接口。如果客户端调用代理中的某个操作, 就会有一个 RPC 消息被发送到服务器,从而调用目标对象上的某个对应的操作。以代 理为中介,客户端发起的调用最终会调用到服务器目标对象上相应的操作。 2. 提供编码(marshaling)和解码(unmarshaling)。编码是将复杂的数据结构串行化,使 其便于网络传输的过程。编码把数据转化为适合于传送的标准形式,这种形式不依赖于 本地机器的字节序和填充规则。解码是编码的逆过程,将通过网络得到的串化数据重新 构造成具有类型的结构化数据。解码之后得到的是与所使用的编程语言相适应的类型表 示的数据。 1.2.6 Ice 骨架(skeleton) 骨架代码也是由用户定义的 Slice 文件经过编译后生成的,其中的内容与 Slice 中定义的对 象和数据的类型是对应的。骨架代码是客户端的代理代码在服务器端的等价物。它提供了向上 调用(up-call)的接口,允许 Ice 把控制线程转交给服务器端的应用程序代码。骨架也负责编码 和解码,所以服务器可以接收客户端发送的参数,并把返回值和异常传回客户。 1.3 Slice 语言 在典型的客户/服务器模式的应用中,开发者使用自己的描述语言或一种公认的标准来定义 设备之间需要使用的协议。协议的定义依赖于具体实现时所用的语言、网络传输的情况和许多 其它的因素。而 Ice 使这个过程变得很简单,在 Ice 中,协议是通过使用 Slice 语言描述相关应 用程序接口来定义的,这是一种十分简单且和编程语言无关的描述语言。 1.3.1 Slice 合约功能 Slice 是一种定义客户和服务器之间规范的基础性机制。每个 Ice 对象都有一个接口,该接 口具有某些操作。接口、操作、客户与服务器间交换的数据类型,都是用 Slice 语言定义的。 Slice 允许开发人员以一种独立于特定编程语言(比如 c++或 java)的方式定义客户端和服务器 端的合约。Slice 定义有特定编译器编译成特定语言的 API,也就是说,与你所定义的接口和数 据类型相对应的那部分 API,会有生成的代码组成。 ICE 中间件 仅供内部使用 第 8 页 2014 年 7 月 2 日 1.3.2 Slice 映射机制 语言映射是一些规则,决定怎样把每个 Slice 成分翻译到特定语言。例如,就 C++映射而言, Slice 序列(sequence)会作为 STL 向量(vector)出现,而就 java 映射而言,Slice 序列会以 java 数组的类型出现。为了确定特定 Slice 成分的 API 是什么样子的,只需要知道 Slice 定义, 并且了解语言映射的规则。这些规则都非常规范,也并不复杂,所以要想知道怎样使用生成的 API,无需去阅读生成的代码。 去阅读生成的代码,效率很低,因为生成的代码并不一定适合人阅读。好的方式还是通过 熟悉语言映射的各种规则,这样,就可以忽略生成的代码,只需在必要时再去参考这些代码。 Ice 目前提供了 C++、Java、.Net、Object-C、Python、PHP、Ruby 的语言映射,其中到 C++的语言映射是线程安全的,并有效地避免了内存泄露的隐患。随着时间的推移,可能会有 更多的语言映射被支持。 1.4 Ice 协议 Slice 是 Ice 对象的描述方式,Ice 核心是 Ice 对象相互通信的中介,而 Ice 协议则是通信的 具体实现。Ice 协议可以建立在多种流行的通信协议之上,定义了客户端和服务器端的 Ice 核心 之间的通信机制。Ice 协议的设计简单、开销很小、同时又具有最广泛的适应性和可扩展性,以 适应不同的网络。 1.4.1 Ice 协议的组成 Ice 提供了一个 RPC(Remote Procedure Call)协议,该协议能够运行在各种流传输和数据 报传输协议上。目前,该协议支持 TCP、UDP 和 SSL 作为底层的传输机制。使用 SSL 协议时, 客户端和服务器端之间的所有通信都可以进行加密,以确保传输的安全性。Ice 协议的引擎是可 以扩展的,开发人员可以通过增加 API 插件来增加新的底层传输协议,而不用修改源代码,Ice 的 SSL 传输就采用了这种插件结构。Ice 协议定义由三个主要部分组成: 1. 数据编码规则:确定各种数据类型的串行化方式。 2. 协议状态机制:规定客户端和服务器如何交互不同类型的信息。 3. 版本控制:确定客户端和服务器怎样就特定的协议和编码版本达成一致。编码规则和 状态机制使用各自独立的版本号,从而保证了良好的向后兼容性。 ICE 中间件 仅供内部使用 第 9 页 2014 年 7 月 2 日 1.4.2 Ice 协议的优势 1. Ice 编码规则简洁紧凑。所有的数据都有固定的字节序,采用 byte 对齐,不作任何边 界填充,保证了编码体积最小化,带宽利用率高。Ice 的协议状态机制十分简单,只 有 5 个消息类型。 2. Ice 支持在线路上进行压缩。通过设置协议头中的一个标志位,可以压缩通信的所有 数据。在带宽资源匮乏的环境里,以及客户端和服务器之间需要交换大量数据时,这 种功能可以有效地节省带宽。 3. Ice 协议还适用于构建高效的事件转发机制。通过 Ice 协议传输的所有数据都是经过封 装的,这种设计机制保证了转发数据时,中间节点不需要了解数据内部的详细信息。 这意味着,中间节点不需要做比特序转换和任何编码解码的工作,他们只是简单的把 消息当作不透明的字节缓冲区加以转发。 4. Ice 协议还适用于构建双向操作。如果服务器想要把一条消息发送给客户端提供的某 个回调对象,这个回调对象可以通过客户端发起调用时创建的连接传送消息。该特性 在穿越防火墙时十分重要。如果客户端位于防火墙后面,通常只允许建立向外的连接 而不允许建立向内的连接,这样一来,服务器回调客户端操作时就会产生问题。 1.4.3 数据编码规则 Ice 数据编码的关键设计目标是简单和高效,遵循这一目标,Ice 编码没有在字边界上对齐 原始类型,因而避免了带宽浪费,也消除了对齐所带来的复杂性。Ice 数据编码产生的就是一个 连续的数据流,数据不含填充字节,也无需再字边界上对齐。在编码时,数据总是使用”little- endian”字节序。 接受者不负责数据的正确性,这样降低了设计的复杂性,同时每个中间接收者都可以转发 数据,而无需进行解码。 1.4.4 协议状态机制 Ice 只有五种协议消息,相对于其它通信协议相当简单。他们分别是: 从客户端发到服务器的请求; 从客户端发到服务器的批请求; ICE 中间件 仅供内部使用 第 10 页 2014 年 7 月 2 日 从服务器端发到客户端的答复; 从服务器端发到客户端的验证连接信息; 双向发送的关闭连接消息; 在这些消息里,验证和关闭连接消息只用于面向连接的传输机制。 和数据编码一样,协议消息也没有对齐限制。除了验证和关闭连接消息。每个消息都由一 个消息头和一个紧跟其后的消息体组成。 1.5 Ice 服务 Ice 核心为分布式应用开发提供了一个完善的平台。实际开发中,应用程序需要的常常不 止是远程通信能力,有时还需要随需启动服务器、把代理分发给客户端、异步分发事件、配置 应用、分发应用补丁等诸如此类的服务。除了核心功能之外,Ice 提供了一些这样的服务。这些 服务被实现成 Ice 服务器,应用程序充当这些服务器的客户端,使用这些服务可以大大的简化 分布式应用程序的开发。这些服务作为 Ice 平台的一部分,使开发人员可以专注于应用程序的 开发,而不必事先构建许多基础设施。下面介绍一下这些服务。 1.5.1 IceFreeze Freeze 是 Ice 的持久化服务,包括两部分 Freeze 映射和 Freeze 驱逐器。所有的持久化数据 都保存在内嵌的 berkeley 数据库中。Freeze 映射是一个相关性容器,应用程序开发人员就可以 在程序中用 Freeze 映射容器存储和读取持久化数据。Freeze 驱逐器的主要功能是维护伺服对象 的持久性。在大型应用程序中,如果所有 Ice 对象的伺服对象都保存在内存中,开销十分巨大。 Freeze 驱逐器维护一个活动伺服对象的队列,如果队列满了,最近最少使用的伺服对象将被驱 逐(保存在数据库中),为新激活的伺服对象在内存中让出位置。 1.5.2 IceGrid IceGrid 是 Ice 定位服务器的一种实现,它从间接绑定的协议-地址对中将一个间接代理中的 符号信息解析出来,IceGrid 除了能够提供简单的服务对象定位功能,还提供了一些网格计算的 特性。 客户端通过代理发起调用请求,代理分为直接代理和间接代理。直接代理中保存有服务器 的地址的详细信息,使用该信息,直接代理可以直接连接到服务器。而间接代理中只包含对象 ICE 中间件 仅供内部使用 第 11 页 2014 年 7 月 2 日 适配器的信息,没有任何服务器端的地址信息。但是,间接代理是一种更为常见的代理方式, 因为间接代理支持服务器的透明迁移,即使服务器地址改变,客户端所持有的已有代理也不会 失效。如果使用的是直接代理,虽然不用为了定位服务器而进行额外的查找,但是如果服务器 被转移到其他机器上,代理就不能继续工作了。可见定位服务器在分布式系统中是相当最重要 的,可以大大提高系统的灵活性。 在 IceGrid 中维护了记录着对象适配器和服务器地址的对应关系映射表。通过检索这张表, 客户端就能获取服务器端当前的地址信息,这个过程类似于通过 DNS 将网络域名映射成 IP 地 址。通过这种间接代理和 IceGrid 相结合的方式,服务器的迁移将不会影响到客户端的正常工作。 IceGrid 提供了对象查找的服务,客户端可用来获取他们感兴趣的对象代理。除此以外,还 提供了服务器激活服务:当客户端发起请求时,服务器可能处于未运行状态,IceGrid 会在第一 个客户请求到达时,随需启动服务器。通过这个服务,开发人员可以轻松配置包含若干服务期 的复杂应用。 1.5.3 IceBox IceBox 是 Ice 服务的应用程序服务器,使用它可以容易地运行和管理动态加载的各种 Ice 服 务。这些服务可能是动态链接库,共享库文件或者 Java 类。IceBox 将已开发的各种服务作为动 态加载的组件配置到一个结合了各种需求的“超级服务器”中,通用的 IceBox 服务代替了单个 的 Ice 服务。IceBox 方便了开发人员对各种服务进行集中管理。 1.5.4 IceStorm 应用系统常常需要把消息分发给多个接收者,由消息发布者直接向消息订阅者发布消息的 架构有一个很大的缺点:发布者和订阅者紧密耦合在一起,为了发送消息,要求发布者了解所 有订阅者的通信细节,如果发送出错,发布者还要进行相应的处理。这种过程相当复杂,但是 这种需求又非常常见。如果能有一种通用的服务,那么应用程序的实现将得到极大的简化。 IceStorm 就是一种这样的消息发布/订阅服务,能够消除消息发布者和订阅者之间的耦合关 系。IceStorm 充当发布者和订阅者的中介。当发布者准备分发消息时,它只需要简单的向 IceStorm 服务器发出一个请求,而不需要对订阅者有任何了解。由 IceStorm 服务器负责把消息 递送给订阅者,IceStorm 还负责处理由于订阅者的行为有问题或订阅者不存在所造成的异常。 ICE 中间件 仅供内部使用 第 12 页 2014 年 7 月 2 日 发布者不再需要关注它的订阅,甚至不需要知道此时是否有订阅者。与此类似,订阅者仅需要 与 IceStorm 服务器进行简单的交互,完成像订阅和取消订阅这样的人物,就可以获得感兴趣的 消息。发布者可以专注于其应用程序特有的功能,而不是管理订阅者这样的琐事。订阅者也可 以集中精力解决对消息的处理,而不用关心其它琐事。要把 IceStorm 整合到应用程序中,消息 的发布者和使用者仅需要做出很少的改动。 发布的消息时按照主题进行的分类,订阅者会订阅感兴趣的主题,只有那些被订阅的主题 才会发布给订阅者,订阅和取消订阅都很简单。IceStorm 支持任意多个主题,主题还可以根据 其 cost 属性结成联盟,IceStorm 作为联盟服务运行时,服务的多个琐实例可以在不同的机器上 运行,使处理负载分摊到许多 CPU 上。IceStorm 服务还允许指定服务标准质量,从而在可靠性 和性能之间做适当的折衷。 IceStorm 实际充当的是事件分发的中介,发布者把消息发布给 IceStorm 服务,由它发布给 订阅者。这样,发布者发布的单个事件就可以发送给多个订阅者,如果需要把消息分发给大量 的应用程序组件,IceStorm 是十分合适的选择。 1.5.5 IcePatch2 IcePatch2 是一种软件修补服务,其之前的实现版本是 IcePatch,为了向下兼容,IcePatch 被 保留。用它把软件更新分发给客户是十分方便的,客户可以简单的连接到 IcePatch2 服务,请求 获得特定应用的更新。这个服务会自动检查客户端软件的版本,并以一种压缩的方式下载任何 需要更新的应用组件,从而节省带宽。可以结合 IceGracier2 服务来保护软件补丁,只让得到授 权的客户下载更新的软件补丁。 1.5.6 IceGlacier2 IceGlacier2 是 Ice 防火墙服务,它能让客户与服务器在不牺牲安全性的前提下,通过防火墙 安全的进行通信,并极大的简化了安全应用程序的配置。用 IceGlacier2 作为服务器端的防火墙, 由 IceGlacier2 来验证和过滤客户端的请求,并以一种安全的方式回调客户端。与 IceSSL 相结合, IceGlacier2 提供了一种非侵入式的、易于配置的、强大的安全解决方案。 1.6 Ice 架构的优势 • 面向对象的语义 ICE 中间件 仅供内部使用 第 13 页 2014 年 7 月 2 日 Ice “在线路上”完全保留了 面向对象范型。所有的操作调用都使用迟后绑定,所以操作 实现的选定,是根据对象在运行时的(而不是静态的)实际类型决定的。 • 支持同步和异步的消息传递 Ice 提供了同步和异步的操作调用(AMI)和分派(AMD),并且通过 IceStorm 提供了 发布-订阅消息传递机制。这样,你可以根据你的应用的需要来选择通信模型,而不必把你 的应用硬塞进某种模型里。 • 支持多个接口 通过 facets,对象可以提供多个不相关的接口,同时又跨越这些接口、保持单一的对象 标识。这提供了极大的灵活性,特别是在这样的情况下:应用在发生演化,但又需要与更老 的、已经部署的客户保持兼容。 • 机器无关性 客户及服务器与底层的机器架构屏蔽开来。对于应用代码而言,像字节序和填充这样的 问题都隐藏了起来。 • 语言无关性 客户和服务器可以分别部署,所用语言也可以不同(目前支持 C++、Java、.Net、 Object-C、Python、Ruby 以及 PHP (客户端))。 客户和服务器所用的 Slice 定义建立两者 之间的接口合约,这样的定义也是它们唯一需要达成一致的东西。 • 实现无关性 客户不知道服务器是怎样实现其对象的。这意味着,在客户部署之后,服务器的实现 可以改变,例如,它可以使用不同的持久机制,甚至不同的程序设计语言。 • 操作系统无关性 Ice API 完全是可移植的,所以同样的源码能够在 Windows 和 UNIX 上编译和运行。 • 线程支持 Ice run time 完全是线程化的,其 API 是线程安全的。 作为应用开发者,(除了在访问 共享数据时进行同步)你无需为开发线程化的高性能客户和服务器付出额外努力。 • 传输机制无关性 ICE 中间件 仅供内部使用 第 14 页 2014 年 7 月 2 日 Ice 目前采用了 TCP/IP 和 UDP 作为传输协议。客户和服务器代码都不需要了解底层 的传输机制(你可以通过一个配置参数选择所需的传输机制)。 • 位置和服务器透明性 Ice run time 会负责定位对象,并管理底层的传输机制,比如打开和关闭连接。客户与 服务器之间的交互显得像是无连接的。如果在客户调用操作时,服务器没有运行,你可以通过 IcePack 让它们随需启动。服务器可以迁移到不同的物理地址,而不会使客户持有的代理失效, 而客户完全不知道对象实现是怎样分布在多个服务器进程上的。 • 安全性 通过 SSL 强加密,可以使客户和服务器完全安全地进行通信,这样,应用可以使用不 安全的网络安全地进行通信。你可以使用 Glacier 穿过防火墙,实现安全的请求转发,并且完全 支持回调。 • 内建的持久机制 使用 Freeze,创建持久的对象实现变成了一件微不足道的事情。Ice 提供了对高性能数 据库 Berkeley DB[18] 的内建支持。 • 开放源码 Ice 的源码是开放的。尽管使用 Ice 平台,并不一定要阅读源码,但通过源码你可以了 解各种事情是怎样实现的,或把这些代码移植到新的操作系统上。 总而言之, Ice 提供了一流的分布式计算开发和部署环境,比我们所知道的其他任何平台 都更完整。 ICE 中间件 仅供内部使用 第 15 页 2014 年 7 月 2 日 2 Slice 语言快览 2.1 简介 Slice 在客户与服务器之间建立合约,描述应用程序所使用的各种类型及对象接口。这种描 述与语言实现无关。Slice 定义由编译器编译到特定的实现语言,编译算法称之为语言映射。编 译器把与语言无关的定义翻译成针对特定语言的类型定义和 API。开发者使用这些类型和 API 来提供应用功能,并与 Ice 交互。Ice 目前支持 C++, Java, .Net, Object-C、Python,PHP,Ruby 等语 言的映射。 由于 Slice 只定义了接口和类型,所以 Slice 是单纯的描述性语言。因此 Slice 中时不能编写 可执行的代码的。Slice 将定义的焦点放在对象接口,以及接口支持的操作和可能产生的异常上。 除此之外,Slice 还支持对象持久化的操作。 2.2 编译 Slice 产生的源代码文件必须和应用程序代码一起生成客户端和服务器端执行程序。开发的 结果就是开发可执行的客户端和服务器端程序。这些可执行的程序可以被发布在任何位置,无 论他们的环境是否相同,它们的实现语言是否相同。这个唯一的约束就是宿主必须提供必要的 运行时环境,例如需要的动态链接库以及客户端和服务器端能够相互连接。 ICE 中间件 仅供内部使用 第 16 页 2014 年 7 月 2 日 2.2.1 客户端和服务器端使用相同的开发环境 图 2.2.1 单开发环境 ICE 中间件 仅供内部使用 第 17 页 2014 年 7 月 2 日 2.2.2 客户端和服务器端使用不同的开发环境 图 2.2.2 多开发环境 2.3 Slice 源文件 • 文件命名 Slice 的源文件以.ice 为扩展名。 对于大小写不区分的系统(例如 DOS),文件的扩展名可以大写,也可以小写,例如 Click.ICE 是有效的;对于大小写敏感的系统(如 Unix),Clock.ICE 是非法的(此时的 扩展名应该小写)。 • 文件格式 Slice 是无格式语言,因此你可以使用空格、横向和纵向制表符、换行符安排你的代码布 局。Slice 中语义和定义的布局没有关联。 • 预处理 Slice 支持#ifndef, #define, #endif 以及#include 预处理指令。不过它们有如下的限制: #ifndef,#define 和#endif 指令的用途是创建双包含的块。例如: ICE 中间件 仅供内部使用 第 18 页 2014 年 7 月 2 日 #ifndef CLOCKICE #define CLOCKICE // #include 指令 //定义 … … #endif CLOCKICE #include 指令只能出现在 Slice 源文件的开始部位。也就是说,#include 必须出现在所有 Slice 定义的前面。此外,#include 指令只能使用<>语法来指定一个要包含的文件名称。 例如: #include Slice 不支持用这些预处理指令做其他用途,也不支持使用 C++预定义指令。 #include 指令允许 Slice 使用其它源文件中的类型定义。Slice 编译器会解析源文件中的所 有代码,其中包括了#include 指令指定的文件中的代码。实际上,编译器只编译编译命 令中指定的顶层文件并生成代码,因此你必须单独编译每一个 inlcude 的文件。 • 定义顺序 Slice 的结构,例如模块,接口或类型定义可以用任何顺序出现。但是定义必须在使用之 前声明。 2.4 词法规则 Slice 的语法规则与 C++和 Java 很相似,除了标示符的差异。 • 注释 Slice 允许使用 C 和 C++的注释风格: /* *C 风格注释 */ //C++风格 • 关键字 Slice 使用小写拼写关键字。例如 class 和 dictionary 都是关键字。不过有两个例外: Object 和 LocalObject 也都是关键字,但是必须如显示的方式拼写。 ICE 中间件 仅供内部使用 第 19 页 2014 年 7 月 2 日 • 标识符 标识符以一个字母起头,后面可以跟随字符和数字。Slice 的标识符被限制在 ASCII 字符 集内并且不支持非英语的字符。 不同于 C++的标识符,Slice 的标识符不能有下划线。这个限制看起来似乎很苛刻,但是 却是有必要的。保留下划线,就让多语言的映射获取了命名空间,从而不会与合法的 Slice 标识符产生冲突。 • 大小写敏感 标识符是大小写不敏感的,但是必须保持拼写一致。例如 TimeOfDay 和 TIMEOFDAY 在同一个命名空间中是一样的。但是,Slice 强制拼写一致。一旦你定义了一个标识符之 后,你必须自始至终的拼写这个标识符的大小写,否则,编译器会认为这是非法的标识 符。这条规则之所以存在,是因为这样就允许 Slice 可以映射到大小写敏感的语言也能映 射到大小写不敏感的语言。 • 是关键字的标识符 你可以使用其他语言的关键字来定义 Slice 标示符,例如,switch 可以用来作为 Slice 标 识符,但是也是 Java 和 C++ 的关键字。Slice 语言映射中针对每一种语言都定义了映射 规则来处理这样的标识符。例如,Slice 把 switch 映射为 C++的_cpp_stitch 和 Java 的 _switch。 • 转义的标识符 你可以用过使用\符号来将 Slice 的关键字转换为标识符。例如 struct dictionary{ //错误的定义 } struct \dictionary{ //正确的定义 } \符号改变了关键字的含义。在上面的例子中,\dictionary 被当作 dictionary 标识符处理。 使用转义的标识符可以允许我们以后加入新的关键字,而不对当前存在的规范造成影响。 • 保留的标识符 ICE 中间件 仅供内部使用 第 20 页 2014 年 7 月 2 日 Slice 将 Ice 以及以 Ice 开始的所有标识符作为保留的标识符。例如,Icecream 将会被认为 是非法的标识符。 同时,Slice 还将以以下标识符为后缀的标识符视为保留的标识符: Helper 、Hodler 、Prx 、Ptr 保留它们,主要是为了防止在生成代码时发生冲突。 2.5 规范速览 Slice 语言定义规范语法和 C++和 Java 都非常类似,这里不做十分详细的介绍,只快 速的介绍 Slice 语言定义规范。如果需要更详细的描述,请参考 Ice 官方文档的相关章节。 2.5.1 模块 Slice 所有定义嵌套进模块中: module ZeroC { module Client { // Definitions here... }; module Server { // Definitions here... }; }; 重新打开模块: module ZeroC { // Definitions here... }; // Possibly in a different source file: module ZeroC { // OK, reopened module // More definitions here... ICE 中间件 仅供内部使用 第 21 页 2014 年 7 月 2 日 }; 2.5.2 基本数据类型 基本类型如下表所列: 类型 取值范围 大小 bool false / true 没指定 byte -128 to 127(0 - 255) >=8bits short -2(15) to 2(15) - 1 >=16bits int -2(31) to 2(31) - 1 >=32bits long -2(63) to 2(63) - 1 >=64bits float IEEE single 标准 >=32bits double IEEE double 标准 >=64bits string 所有的 Unicode 字符, 除了所有位为 0 的字符 变长 Slice 提供了整数类型 short、int,以及 long,没有提供无符号类型。 Slice 串使用的是 Unicode 字符集。唯一一个不能出现在串中的字符是零字符。 Slice 的 byte 类型是一种(至少) 8 位的类型,当在地址空间之间传送时,它保证不会发 生任何改变。 2.5.3 枚举 enum Fruit { Apple, Pear, Orange }; Slice 没有定义枚举的顺序值。 2.5.4 结构 struct TimeOfDay { short hour; // 0 - 23 short minute; // 0 - 59 short second; // 0 - 59 }; Slice 类型定义,除模块外不能嵌套;因此结构不能嵌套定义,但可以这样定义: ICE 中间件 仅供内部使用 第 22 页 2014 年 7 月 2 日 struct Point { short x; short y; }; struct TwoPoints { // Legal (and cleaner!) Point coord1; Point coord2; }; 2.5.5 序列 变长的元素向量 sequence FruitPlatter; sequence FruitBanquet; 2.5.6 词典 从键类型到值类型的映射(Key-Value 对) dictionary EmployeeMap; 词典的键类型无需为整型 dictionary WeekdaysEnglishToGerman; 不能使用浮点值或嵌套结构作键 2.5.7 接口 interface Clock { TimeOfDay getTime(); void setTime(TimeOfDay time); ICE 中间件 仅供内部使用 第 23 页 2014 年 7 月 2 日 }; 2.5.8 参数 定义的操作既有输入参数,又有输出参数,输出参数必须放在输入参数的后面: void changeSleepPeriod( TimeOfDay startTime, TimeOfDay stopTime, out TimeOfDay prevStartTime, out TimeOfDay prevStopTime); Slice 不支持既是输入、又是输出参数的参数。 Slice 不支持操作重载,同一接口中的各个操作必须具有不同的名称。 2.5.9 Idempotent(幂等操作) 多次执行同一操作,结果不变,关键字 idempotent interface Clock { idempotent TimeOfDay getTime(); idempotent void setTime(TimeOfDay time); }; 2.5.10 nonmutating 操作 不修改他们所操作的对象的状态,在概念上和 C++的 const 成员变量是等价的 interface Clock { idempotent TimeOfDay getTime(); nonmutating void setTime(TimeOfDay time); }; 2.5.11 用户异常 异常可以为空 exception Error {}; ICE 中间件 仅供内部使用 第 24 页 2014 年 7 月 2 日 exception RangeError { TimeOfDay errorTime; TimeOfDay minTime; TimeOfDay maxTime; }; interface Clock { idempotent TimeOfDay getTime(); idempotent void setTime(TimeOfDay time) throws RangeError, Error; }; 异常可以继承。在运行时,可以抛出任何与异常规范中列出的异常类型兼容的异常。 如果客户端不识别接收的派生异常,只识别基异常,就将接收的异常切成基异常。 Ice:Exception → LocalException → Run-time Exception ↘UserException 主要的运行时异常: • ObjectNotExistException:找不到对象 • FacetNotExistException:找不到层面 • OperationNotExistException:找到服务者,但找不到操作。 服务器端错误产生的一般异常: •UnknownUserException:抛出的 Slice 异常没有在异常规范中声明。 •UnknownLocalException:上述 a,b,c 三种异常之外的运行时异常 •UnknownException:非 Ice 异常,如 C++异常。 2.5.12 代理 interface Clock { idempotent TimeOfDay getTime(); ICE 中间件 仅供内部使用 第 25 页 2014 年 7 月 2 日 idempotent void setTime(TimeOfDay time); }; dictionary TimeMap; // 时区 — 时钟 interface WorldTime { idempotent void addZone(string zoneName, Clock* zoneClock); void removeZone(string zoneName) throws BadZoneName; idempotent Clock* findZone(string zoneName) throws BadZoneName; idempotent TimeMap listZones(); idempotent void setZones(TimeMap zones); }; Clock* —> 接口* —> 代理:类似 C++对象指针 * 称为代理操作符,*号左边必须是接口/类 2.5.13 接口继承 interface AlarmClock extends Clock { idempotent TimeOfDay getAlarmTime(); idempotent void setAlarmTime(TimeOfDay alarmTime); }; 接口可以多继承: interface RadioClock extends Radio, AlarmClock .... 多个接口不能含有同名操作。即如果 Radio 中有 set 操作,AlarmClock 中有 set 操作,则 RadioClock 不能从两个接口继承。 所有接口最终都派生自 Object。 Ice 支持 null 代理。 不要定义空接口,在设计上是错误的。 ICE 中间件 仅供内部使用 第 26 页 2014 年 7 月 2 日 2.5.14 类 类允许你在客户端实现行为,而接口只允许你在服务器端实现行为。 不要定义空类,在设计上是错误的。 class TimeOfDay {...} class DateTime extends TimeOfDay {...} 类只支持单继承,下面的定义是错误的: class DateTime extends TimeOfDay, Date {...} 派生类不能重新定义基类数据成员。 类的操作是本地操作,调用类上的操作并不会产生远程过程调用。在线上传输类时,Ice 运行时只整编类的数据成员。接收者在自己的地址空间里实例化这个类,负责为类提供 操作的实现代码。即提供一个类工厂。 一旦你使用了有操作的类,你实际上就是在使用客户端原生代码,因此,你不再能享受 到接口所提供的实现透明性。建议最好使用接口和没有操作的类。 class Clock implements Time {...} 类实现接口 class RadioClock implements Time, Radio {...} 类实现多个接口 class RadioAlarmClock extends Clock implements AlarmClock, Radio {...}类继承及实现 类不能重定义基接口或基类继承的操作或数据成员。 interface Example { TimeOfDay* get(); }; get 返回 TimeOfDay 类代理。客户可以通过这个代理调用操作,但不能访问数据成员。 这是因为代理没有数据成员的概念。 定义操作时,将接口作为参数,使得接口以传值方式传递。由于接口在实现时是抽象的, 因此实参应该传入实现接口的类。 ICE 中间件 仅供内部使用 第 27 页 2014 年 7 月 2 日 2.5.15 提前声明 interface Time; class TimeOfDay; 类型 ID ::MyModule::Child; Object 的操作 •ice_ping 测试某个对象是否可到达 •ice_isA 测试目标对象是否支持指定的类型 •ice_id 接口的派生层次最深的类型 ID •ice_ids 含有某个接口所支持的所有类型 ID 的序列 2.5.16 本地类型 local 关键字定义了只在本地访问的 API。Slice 编译器不会为相应的类型生成整编代码。 这意味着,本地类型永远不能从远程访问,因为它不能在客户和服务器之间传送。 local 主要用于 Ice 运行时的各种 API。 2.5.17 名字与作用域 如果两个标识符只有大小写不同,将被认为是相同的。Slice 编译器还要求你在使用标识 符时,始终使用同样的大小写。否则会产生编译错误。 2.5.18 元数据指令 ["java:type:java.util.LinkedList"] sequence IntSeq; 指示编译 Java 程序时,使用 LinkedList 表示序列。 全局元数据指令: [["java:package:com.acme"]] 所有使用本 Slice 定义的 Java 程序,引入包名 com.acme 废弃的 Slice 定义: ICE 中间件 仅供内部使用 第 28 页 2014 年 7 月 2 日 ["deprecated:....."] 冒号跟着警告消息,可忽略。 2.5.19 Slice 检查和 检查 C/S 两端的 Slice 定义是否一致: #include interface MyServer { idempotent Ice::SliceChecksumDict getSliceChecksums(); // ... }; 词典类型:dictionary SliceChecksumDict; 词典中每个元素的键是一个 Slice 类型 ID,并且值是该类型的检查和。 ICE 中间件 仅供内部使用 第 29 页 2014 年 7 月 2 日 3 ICE 应用—开发初探 3.1 Hello Wrold 应用 3.1.1 实现目的 这个应用提供远地打印功能:客户端发送要打印的文本,服务器端接收到该文本,再由服 务器把这个文本输出到控制台。通过这个简单的实现,介绍如何使用 Ice:开发环境配置、开发 步骤、实现代码细节(实现细节通过代码注释给出)等。下面简单介绍一下开发环境配置: 1. 下载开发平台、开发语言对应的 Ice 包。下载地址为: http://www.zeroc.com/download.html 2. 安装 Ice 开发包 3. 定义 Ice 的 home 目录为 ICE_HOME 环境变量 4. 将 Ice 的 bin 目录设置到 path 环境变量上:%ICE_HOME%/bin 5. 配置开发语言 IDE 环境,引入 Ice 需要的开发库 当前实现平台是 window,开发语言为 C++和 java,相应 IDE 为 Microsoft Visual studio 10 和, MyElipse8.5 3.1.2 C++实现 3.1.2.1 定义 Slice 文件 Printer.ice 定义如下: #ifndef SIMPLE_ICE #define SIMPLE_ICE module Demo { interface Printer { void printString(string s); }; }; #endif ICE 中间件 仅供内部使用 第 30 页 2014 年 7 月 2 日 本 Slice 文件定义了模块 Demo,它含有 Printer 接口,定义了 printString 操作,printString 操 作接受一个字符串。程序功能是客户端调用 printString 发送字符串给服务器,服务器将字符串 打印出来。 3.1.2.2 编译 Slice 文件 编译 Printer.ice 文件(slice2cpp Printer.ice),生成 Printer.h/Printer.cpp 文件,定义了接口的 规范,实现了运行时支持的类型以及类型的整编与解编。在我们编写的 C/S 程序中引用了这些 文件,以便使用接口。 3.1.2.3 服务器端 Server.cpp: //必须包含 Ice.h 文件,以支持 Ice 运行时 #include //必须包含 Printer.h,以使用 Slice 规范 #include using namespace std; using namespace Demo; /* 骨架类 Printer 根据 Printer.ice 定义由 Slice2cpp 编译器生成, 它的 printString 是纯虚方法,骨架类不能被实例化。 服务者 PrinterI 类继承自骨架类 Printer,提供了 printString 纯虚方法的实现。 */ class PrinterI : public Printer { public: virtual void printString(const string & s,const Ice::Current &); }; /* Ice:Current 对象为服务器中操作的实现提供了有关当前执行请求的信息, 如适配器,连接,标识,层面,操作,操作模式,上下文,请求 ID 等。 */ ICE 中间件 仅供内部使用 第 31 页 2014 年 7 月 2 日 void PrinterI::printString(const string & s, const Ice::Current &) { cout << s << endl; } int main(int argc, char* argv[]) { int status = 0; Ice::CommunicatorPtr ic; try { //初始化 Ice,获取通讯器,智能指针 ic = Ice::initialize(argc, argv); //在通讯器 ic 上创建对象适配器 SimplePrinterAdapter //缺省协议(TCP),在端口 10000 上监听 Ice::ObjectAdapterPtr adapter = ic->createObjectAdapterWithEndpoints("SimplePrinterAdapter", "default -p 10000"); //创建服务者类 PrinterI Ice::ObjectPtr object = new PrinterI; //将服务者类 PrinterI 加入对象适配器,服务者类的名称是 SimplePrinter, //客户端必须使用此名称的请求标识来请求服务者的操作。 adapter->add(object,ic->stringToIdentity("SimplePrinter")); //激活对象适配器,客户端请求现在可以分派给服务者类 PrinterI。 adapter->activate(); //挂起当前主函数所在线程,等待通讯器关闭 ic->waitForShutdown(); } catch (const Ice::Exception & e) { //捕捉 Ice 运行时抛出的异常 cerr << e << endl; status = 1; } catch (const char * msg) {//捕捉致命错误,输出错误串 cerr << msg << endl; ICE 中间件 仅供内部使用 第 32 页 2014 年 7 月 2 日 status = 1; } //通讯器必须初始化过,否则不能调用 destroy if (ic) { try { ic->destroy();//销毁通讯器 } catch (const Ice::Exception & e) { cerr << e << endl; status = 1; } } return status; } 3.1.2.4 客户端 Client.cpp #include #include using namespace std; using namespace Demo; int main(int argc, char * argv[]) { int status = 0; Ice::CommunicatorPtr ic; try { //初始化 Ice,获取通讯器,智能指针 ic = Ice::initialize(argc, argv); //创建通讯器的代理基类,该代理使用的请求标识为 SimplePrinter //端口号:10000,协议:默认(TCP) Ice::ObjectPrx base = ic->stringToProxy("SimplePrinter:default -p 10000"); ICE 中间件 仅供内部使用 第 33 页 2014 年 7 月 2 日 //代理基类向下转型为 PrinterPrx 代理类,调用本操作会向服务器发送请求。 //如果转型成功就返回 Printer 接口的代理,如果转型失败就返回 null 代理。 PrinterPrx printer = PrinterPrx::checkedCast(base); //不是 Printer 接口的代理,抛出异常 if (!printer) throw "Invalid proxy"; //是 Printer 接口的代理,调用代理的操作 printString printer->printString("Hello World!"); } catch (const Ice::Exception & ex) {//捕获 Ice 运行时异常 cerr << ex << endl; status = 1; } catch (const char * msg) {//捕捉致命错误,输出错误串 cerr << msg << endl; status = 1; } //通讯器必须初始化过,否则不能调用 destroy if (ic) { try { ic->destroy();//销毁通讯器 } catch (const Ice::Exception & ex) { cerr << ex << endl; status = 1; } } return status; } 3.1.2.5 运行 客户端和服务器使用 VC++的工程文件完成编译,然后先运行 Server,再运行 Client。关闭 时,以 Ctrl+C 结束 Server 程序。 ICE 中间件 仅供内部使用 第 34 页 2014 年 7 月 2 日 3.1.3 Java 实现 3.1.3.1 定义 Slice 文件 Printer.ice 定义如下(同 C++实现中的完全一样): #ifndef SIMPLE_ICE #define SIMPLE_ICE module Demo { interface Printer { void printString(string s); }; }; #endif 本 Slice 文件定义了模块 Demo,它含有 Printer 接口,定义了 printString 操作,printString 操 作接受一个字符串。程序功能是客户端调用 printString 发送字符串给服务器,服务器将字符串 打印出来。 3.1.3.2 编译 Slice 文件 编译 Printer.ice 文件(slice2java Printer.ice),会在当前目录产生一个 Demo 目录,目录里自 动生成如下文件: _PrinterDel.java _PrinterDelD.java _PrinterDelM.java _PrinterDisp.java _PrinterOperations.java _PrinterOperationsNC.java Printer.java PrinterHolder.java PrinterPrx.java PrinterPrxHelper.java PrinterPrxHolder.java ICE 中间件 仅供内部使用 第 35 页 2014 年 7 月 2 日 3.1.3.3 服务器端 3.1.3.3.1 接口实现 PrinterI.java package slice2java; import Ice.Current; import Demo._PrinterDisp; /** * _PrinterDisp 基类由 slice2java 编译器生成,它是一个抽象类。 * _PrinterDisp 抽象类包含一个 printString()方法。 */ public class PrinterI extends _PrinterDisp { public void printString(String s, Current current) { System.out.println(s + " ice !"); } } 3.1.3.3.2 服务器主程序 Server.java package server; import slice2java.PrinterI; import Ice.Util; import Ice.ObjectAdapter; import Ice.Object; import Ice.Communicator; public class Server { public static void main(String[] args) { int status = 0; // 创建一个通信器的对象 ic Communicator ic = null; try { // 初始化 Ice 运行时 ic = Util.initialize(); /* * 创建一个对象适配器(ObjectAdapter)对象 adapter,并初始化之。 * 参数"SimplePrinterAdapter":表示适配器的名字。 * 参数"default -p 10000":表示适配器使用缺省协议(TCP/IP) *在端口 10000 处监听到来的请求。 */ ICE 中间件 仅供内部使用 第 36 页 2014 年 7 月 2 日 ObjectAdapter adapter = ic.createObjectAdapterWithEndpoints("SimplePrinterAdapter", "default -p 10000"); Object object = new PrinterI(); // 将 object 添加到适配器,并命名为"SimplePrinter" adapter.add(object, ic.stringToIdentity("SimplePrinter")); // 激活适配器,以使服务器开始处理来自客户的请求 adapter.activate(); /* * 挂起发出调用的线程,直到服务器实现终止为止. * 或者是通过发出一个调用关闭运行时(run time)的指令来使服务器终止. */ ic.waitForShutdown(); } catch (Ice.LocalException e) { e.printStackTrace(); status = 1; } catch (Exception e) { System.out.println(e.getMessage()); status = 1; } if (ic != null) { // Clean up // try { // 销毁通信连接 ic.destroy(); } catch (Exception e) { System.out.println(e.getMessage()); status = 1; } } System.exit(status); } } 3.1.3.4 客户端 Client.java package client; import Ice.Util ; import Demo.PrinterPrxHelper ; import Ice.ObjectPrx ; import Demo.PrinterPrx ; import Ice.Communicator ; public class Client { ICE 中间件 仅供内部使用 第 37 页 2014 年 7 月 2 日 public static void main(String[] args) { int status = 0; Communicator ic = null; try { //创建一个通信器的对象 ic ic = Util.initialize(); /* * 获取远程对象的代理 *创建通讯器的代理基类,并用通信器的 stringToProxy()方法初始化之. *该代理使用的请求标识为 SimplePrinter, 端口号:10000,协议:默认(TCP) */ ObjectPrx base = ic .stringToProxy("SimplePrinter:default -p 10000"); PrinterPrx printer = PrinterPrxHelper.checkedCast(base); if (printer == null) throw new Error("Invalid ice"); printer.printString("Hello World "); } catch (Ice.LocalException e) { e.printStackTrace(); status = 1; } catch (Exception e) { System.out.println(e.getMessage()); status = 1; } if (ic != null) { //通讯器必须初始化过,否则不能调用 destroy try { ic.destroy();//销毁通讯器 } catch (Exception e) { System.out.println(e.getMessage()); status = 1; } } System.exit(status); } ICE 中间件 仅供内部使用 第 38 页 2014 年 7 月 2 日 } 3.1.3.5 运行 客户端和服务器使用 MyEclipse 完成编译,然后先运行 Server,再运行 Client。关闭时,以 Ctrl+C 结束 Server 程序。 3.2 总结 本章介绍了一个非常简单、完整的客户/服务器程序,并且分别用 C++和 Java 来实现。可以 看出 C++和 Java 的实现过程是十分类似的,差别只是语言特性的不同。下面对开发步骤做一个 总结: 1. 编写 Slice 文件定义服务接口和操作  xxxx.ice 2. 编译 Slice 文件(slice2xxx)Ice 接口文件或目录 3. 编写服务器端程序并编译它 4. 编写客户端程序并编译它 ICE 中间件 仅供内部使用 第 39 页 2014 年 7 月 2 日 4 Ice 应用—开发基础 4.1 本章目的 本章更进一步的介绍 Ice 应用程序开发的基本方法、步骤,主要针对 C++语言的实现。如 果需要,以后可以再增加针对其它语言的介绍。首先快速介绍了客户端和服务器端 Slice 到 C++ 的映射规则,随后通过设计和实现一个简单的文件系统来掌握 Ice 应有程序开发的基本方法和 步骤。 4.2 客户端 Slice 到 C++的映射 客户端 Slice 到 C++映射定义的是:怎样把 Slice 数据类型翻译成 C++类型,客户怎样调用 操作、传递参数、处理异常。C++映射线程安全,不存在内存管理问题。不建议查看 slice2cpp 生成的 C++映射文件,但建议掌握 C++映射规则。 4.2.1 模块映射 module M{...} 映射为: namespace M{...} 4.2.2 类型映射 1.基本类型 Slice C++ bool → bool byte → Ice::Byte short → Ice::Short int → Ice::Int long → Ice::Long float → Ice::Float double → Ice::Double string → std::string ICE 中间件 仅供内部使用 第 40 页 2014 年 7 月 2 日 2.复杂类型 Slice C++ 枚举 → 枚举 结构 → 结构,支持比较符,可用于 Slice 词典键类型,支持深度复制、赋值。 序列 → std::vector,可以映射为 list,dequeue,数组,范围。 词典 → std::map。 4.2.3 异常映射 异常可映射为同名 C++类,支持如下操作: • ice_name 异常的名字。 • ice_clone 多态克隆异常,在不知道异常类型情况下复制异常。 • ice_throw 在不知道某个异常的确切运行时类型的情况下抛出它。 • ice_print 打印异常的名字,或直接使用重载的<<操作符: try { // ... } catch (const Ice::Exception & e) { cerr << e << endl; } 出于效率上的考虑,总是通过 const 引用捕捉异常。这样,编译器就能够生成不调用异常 复制构造器的代码(当然,同时也防止异常被切成基类型)。调用操作抛出异常时,该 操作的 out 参数是否赋值无法确定,但是操作的返回值肯定没有返回。 一条异常路径: • Ice::ConnectTimeoutException • Ice::TimeoutException • Ice::LocalException • Ice::UserException • Ice::Exception 很少需要以最深派生类型捕获运行时异常,而是以 LocalException 捕获它们;对细粒度 异常处理感兴趣的,主要是 Ice 运行时实现。 ICE 中间件 仅供内部使用 第 41 页 2014 年 7 月 2 日 4.2.4 接口映射 module M { interface Simple { void op(); }; }; 客户端,接口映射到类: namespace IceProxy { namespace M { class Simple; } } namespace M{ class Simple; — 代理类 typedef IceInternal::ProxyHandle< ::IceProxy::M::Simple> SimplePrx; — 代理句柄 typedef IceInternal::Handle< ::M::Simple> SimplePtr; } namespace IceProxy { namespace M { class Simple : public virtual IceProxy::Ice::Object { public: typedef ::M::SimplePrx ProxyType; typedef ::M::SimplePtr PointerType; void op(); void op(const Ice::Context&); //.... }; }; } IceProxy::M::Simple 是服务器 Simple 接口的客户端代理类,它继承自 IceProxy::Ice::Object。接口中的每个操作,代理类都有两个重载的、同名的成员函数。 ICE 中间件 仅供内部使用 第 42 页 2014 年 7 月 2 日 其中一个函数的最后一个参数的类型是 Ice::Context——Ice 上下文,dictionary结构,客户端向服务器发送请求时,将该结构一起发送。 客户端不能直接实例化代理类: IceProxy::M::Simple s; ← 错误 客户端只能使用代理句柄 SimplePrx 访问代理类 Simple。代理句柄能够自动管理内存, 支持赋值和比较操作符: SimplePrx prx1 = ...; SimplePrx prx2(prx1); prx1=prx2; assert(prx1==prx2); cout << prx1 << ';' << prx2 << endl; ← 等效方法 prx->ice_toString() 类似方法: communicator->proxyToString(prx); BasePrx base = ...; DerivedPrx derived = DerivedPrx::checkedCast(base); ← 检查转换:远程访问,失败 时返回 null。 derived = DerivedPrx::uncheckedCast(base); ← 不检查的转换:不进行远程访问,失 败时行为不确定。 代理方法: Ice::ObjectPrx base = communicator->stringToProxy(...); HelloPrx hello = HelloPrx::checkedCast(base); hello = hello->ice_timeout(10000); hello->sayHello(); 代理比较: 代理同一性比较:==,!=,<,<=,>,>=比较,布尔比较; 代理的对象同一性比较: bool proxyIdentityLess(p1,p2); bool proxyIdentityEqual(p1,p2); ICE 中间件 仅供内部使用 第 43 页 2014 年 7 月 2 日 bool proxyIdentityAndFacetLess(p1,p2); bool proxyIdentityAndFacetEqual(p1,p2); 4.2.5 操作映射 对于所有被映射的 Slice 类型而言都一样: 你可以安全地忽略操作的返回值,不管它的类型是什么——返回值总是通过传值返回。 如果你忽略返回值,不会发生内存泄漏,因为 返回值的析构器会按照需要释放内存。 in 参数,使用传值或 const 引用传递; out 参数,使用引用传递。 支持链式调用: p2->setName(p1->getName()); ← p1,p2 为代理 4.2.6 异常处理 SimplePrx p = ...; try { p->op(); } catch (const SimpleError & t) { cout << "Simple Error: " << t.reason << endl; } 应该总是使用 const 引用捕捉异常。这样,编译器就能生成不调用异常复制构造器的代码, 同时防止异常切成基类型。操作抛出异常后,操作的参数是否已改变不能确定;但是接收操作 返回值的变量没有被改写。 4.2.7 类映射 •Slice 类映射到同名 C++类 • 每个成员映射为 C++类 public 成员; • 每个操作映射为虚成员函数; • 类继承自 Ice::Object(代理类继承自 Ice::ObjectPrx); • 两个构造器,一个缺省,一个带有所有成员参数; ICE 中间件 仅供内部使用 第 44 页 2014 年 7 月 2 日 • 生成Ptr 智能指针(Prx 是代理句柄)。 类继承自 Ice::Object 基类的成员函数: • ice_isA:是否支持指定类型 ID • ice_ping:类可达测试 • ice_ids:对象支持的类序列,从根类到最深派生类 • ice_id:类的实际 ID • ice_staticId:类的静态类型 ID • ice_hash:类的哈希值 • ice_clone:多态浅复制 • ice_preMarshal:整编本类前调用 • ice_postUnmarshal:解编本类后调用 • ice_dispatch:请求分派给服务者 • 比较操作符:== != < <= > >= 类成员可见性: 修改类成员的可见性,使用元数据指令:["protected"] 类的构造函数: 类的缺省构造函数,构造每个数据成员,简单内置类型不初始化,复杂类型使用 该类型自己的缺省构造初始化。 类的非缺省构造函数,每个数据成员一个参数,可以用一条语句构造和初始化。 所有参数按照基类到派生类的顺序,加入参数序列。 类的操作: 类的操作被映射为纯虚成员函数,必须创建派生类,实现这些操作,才能使用类。 类工厂: 有操作的类必须提供类工厂,无操作的类不必提供类工厂。 实现类工厂 ICE 中间件 仅供内部使用 第 45 页 2014 年 7 月 2 日 class SimpleFactory : public Ice::ObjectFactory { public: virtual Ice::ObjectPtr create(const std::string &) { assert(type == M::Simple::ice_staticId()); return new SimpleI; } virtual void destroy() {} }; 注册类工厂: Ice::CommunicatorPtr ic = ...; ic->addObjectFactory(new SimpleFactory, M::Simple::ice_staticId()); 使用类工厂: 现在,每当 Ice 实例化 M::Simple 类时,就会自动调用 SimpleFactory 的 create 方法, 创建 SimpleI 类,客户端可以使用该类的 op 方法。 类的智能指针: Slice 编译器会为每种类类型生成智能指针。对于 Slice 类,编译器会 生成叫作Ptr 的 C++ 智能指针: TimeOfDayPtr tod = new TimeOfDayI; 不能在栈上分配类实例,也不能用静态变量定义类实例。类必须由 new 分配。 智能指针是异常安全的,当抛出异常时,智能指针能够安全地析构。但要注意:在构造 器中抛出异常,可能使得智能指针不安全;循环引用的情况下,智能指针也会不安全, 这种情况下,可以使用 Ice::collectGarbage(); 收集因循环引用没有释放的对象。当然只有 在循环引用的情况下,垃圾收集器才有用。在没有循环引用的情况下,使用垃圾收集器 没有意义。把 Ice.GC.Interval 设成 5,收集器线程就会每 5 秒运行一次垃圾收集器。 智能指针的比较只会比较内存地址,即比较两个指针是否指向同一物理类实例。 4.3 服务器端 Slice 到 C++的映射 ICE 中间件 仅供内部使用 第 46 页 2014 年 7 月 2 日 本文将介绍服务器端的 Slice 到 C++ 映射。客户端 Slice 到 C++映射的内容也适用于服务器 端。 4.3.1 服务器端程序流程 从 ICE 的 Hello World 应用程序中可以看到,Ice 运行时的主入口点为 Ice::Communicator。 程序必须首先调用 Ice::initialize,对 Ice 运行时进行初始化。Ice::initialize 返回一个指向 Ice::Communicator 实例的智能指针。根据该指针调用 Ice 提供的各种服务,实现各项功能。 当关闭 Ice 程序时,必须调用 Communicator::destroy。destroy 会确保还在执行的操作完成调 用,收回操作系统资源,如果未调用 destroy 就终止程序;这样做会导致不确定的行为。 整个流程结构是公式化的代码,Ice 提供了 Ice::Application 类,封装了 Ice 运行时的初始、 销毁操作。此后,我们将主要使用 Ice:Application 类,以减少编码工作。 4.3.2 Ice::Application 类 Applicaiton 类的使用: class MyApplication : virtual public Ice::Application { public: virtual int run(int, char * []) { // Server code here... return 0; } }; int main(int argc, char * argv[]) { MyApplication app; return app.main(argc, argv); } Application 类的定义: namespace Ice { enum SignalPolicy { HandleSignals, NoSignalHandling }; class Application /* ... */ { public: ICE 中间件 仅供内部使用 第 47 页 2014 年 7 月 2 日 Application(SignalPolicy = HandleSignals); virtual ~Application(); int main(int argc, char*[] argv); int main(int, char*[], const char* config); int main(int argc, char*[] argv,const Ice::InitializationData& id); int main(const Ice::StringSeq&); int main(const Ice::StringSeq&, const char* config); int main(const Ice::StringSeq&,const Ice::InitializationData& id); virtual int run(int, char*[]) = 0; static const char* appName(); static CommunicatorPtr communicator(); // ... }; } Application::main 函数的功能: • 安装了 Ice::Exception 异常处理器。Ice 异常失败,打印到 stderr。 • 安装了 const std::string & 和 const char * 异常处理器。致命错误情况打印到 stderr。 • 初始化(通过调用 Ice::initialize)和结束(通过调用 Communicator::destroy)通信器。 用 communicator() 访问通信器。 • 处理并删除 Ice 相关参数,传给 run 方法的参数向量只与特定应用程序相关。 • appName 返回 argv[0],应用程序名。 • 创建了 IceUtil::CtrlCHandler,处理中止信号。 • 安装了每进程日志器。以 Ice.ProgramName 为前缀,日志输出 stderr。使用 InitializationData 结构,可以指定日志器。 Application 类捕获信号的操作: namespace Ice { class Application : /* ... */ { ICE 中间件 仅供内部使用 第 48 页 2014 年 7 月 2 日 public: // ... static void destroyOnInterrupt(); static void shutdownOnInterrupt(); static void ignoreInterrupt(); static void holdInterrupt(); static void releaseInterrupt(); static bool interrupted(); virtual void interruptCallback(int); }; } IceUtil::CtrlCHandler 类中封装了平台无关的信号处理能力。 Unix:SIGINT、SIGHUP,SIGTERM; Windows:CTRL_C_EVENT、CTRL_BREAK_EVENT、CTRL_CLOSE_EVENT、 CTRL_LOGOFF_EVENT,CTRL_SHUTDOWN_EVENT。 destroyOnInterrupt: 创建 IceUtil::CtrlCHandler,当信号产生时销毁通讯器。这是缺省行为。 shutdownOnInterrupt: 创建 IceUtil::CtrlCHandler,当信号产生时关闭通讯器。 ignoreInterrupt: 忽略信号 callbackOnInterrupt: 信号发生时,Ice::Application 调用 interruptCallback,以便子类处理信号 。如果信号处理 器要终止程序,必须调用_exit,而不是 exit。 holdInterrupt: 阻塞信号 releaseInterrupt: 发送被阻塞的信号 interrupted: 信号关闭了通讯器,返回 true;否则返回 false,表示正常有关闭。 ICE 中间件 仅供内部使用 第 49 页 2014 年 7 月 2 日 interruptCallback 子类重写本函数,响应信号。 MyApplication app; //或 app(HandleSignals); 默认处理信号 MyApplication app(NoSignalHandling); //不处理信号 如果服务器被信号中断,Ice 运行时会等待目前正在执行的所有操作完成。才会完成信号处 理。Application 负责用属性值初始化 Ice 运行时,它是单例类,创建单个通信器。如果要创建多 个通讯器,必须使用类似 Ice 的 Hello World 程序那样的常规代码结构。 4.3.3 Ice::Service 类 类似 Application 类,可以作为系统服务运行。要实现这项功能,至少要重写 start 类。 Service 类的使用: #include class MyService : public Ice::Service { protected: virtual bool start(int, char * []); private: Ice::ObjectAdapterPtr _adapter; }; bool MyService::start(int argc, char * argv[]) { _adapter = communicator()->createObjectAdapter("MyAdapter"); _adapter->addWithUUID(new MyServantI); _adapter->activate(); return true; } int main(int argc, char * argv[]) { MyService svc; return svc.main(argc, argv); } ICE 中间件 仅供内部使用 第 50 页 2014 年 7 月 2 日 Service 类的定义: namespace Ice { class Service { public: Service(); virtual bool shutdown(); virtual void interrupt(); int main(int&, char*[],const Ice::InitializationData&=Ice::InitializationData()); int main(Ice::StringSeq&,const Ice::InitializationData&=Ice::InitializationData()); Ice::CommunicatorPtr communicator() const; static Service * instance(); bool service() const; std::string name() const; bool checkSystem() const; int run(int&, char*[], const Ice::InitializationData&); void configureService(const std::string&); void configureDaemon(bool, bool, const std::string&); virtual void handleInterrupt(int); protected: virtual bool start(int, char * []) = 0; virtual void waitForShutdown(); virtual bool stop(); virtual Ice::CommunicatorPtr initializeCommunicator(int &,char * [], const Ice::InitializationData&); virtual void syserror(const std::string &); virtual void error(const std::string &); virtual void warning(const std::string &); virtual void trace(const std::string &); ICE 中间件 仅供内部使用 第 51 页 2014 年 7 月 2 日 void enableInterrupt(); void disableInterrupt(); // ... }; } Service.main 分析处理参数向量 配置 configureService/configureDaemon 运行 run Service.run 安装 CtrlCHandler 处理信号 初始化通讯器 调用 start 调用 waitForShutdown 调用 stop 销毁通讯器 得体地终止服务 Service 成员函数: handleInterrupt 信号发生时由 CtrlCHandler 调用,缺省忽略信号。 initializeCommunicator 初始化通讯器,缺省 Ice::initialize interrupt 信号处理器调用它表示收到信号, 缺省实现调用 shutdown。 shutdown 关闭服务器 start 启动:参数处理,创建对象适配器,注册服务者…… stop 终止之前的清理 syserror,error,warning,trace,print 将消息记录到通讯器日志器中。 waitForShutdown 等待服务关闭,缺省实现是调用通讯器的 waitForShutdown。 checkSystem 支持服务,返回 true。 ICE 中间件 仅供内部使用 第 52 页 2014 年 7 月 2 日 disableInterrupt 禁用信号 enableInterrupt 启用信号, handleInterrupt 处理 configureDaemon configureService *instance 服务实例 name 服务名称 run 必须设置 configureDeamon/configService 作为服务运行。 service 是服务,返回 true。 日志 Windows 上,不设置日志器,会记录到系统事件日志中。 Unix 上,使用 Ice.UseSyslog 指日志器,Ice.StdErr 将错误重定向到文件。 命令行参数: Unix 平台上 --daemon 参数指明程序应该作为守护程序运行。 Windows 平台上 --service NAME 参数指明程序作为 NAME 服务运行。 有关 Ice::Application 和 Ice::Service 相关信息还可以参考 Ice 官方文档。 4.3.4 接口映射 在客户端,接口映射到代理类。在服务器端,接口映射到骨架类。骨架类是抽象类,成员 函数为纯虚函数,继承自 Ice::Object。 服务者类继承并实现对应的骨架类。在定义服务者类时建议总是使用虚继承。严格地说, 只有其实现的接口使用了多继承的服务者才必须使用虚继承;但 virtual 关键字并无害处,同时, 如果在开发中途要给接口层次增加多继承,无需回去给服务者类增加 virtual 关键字。 4.3.5 参数传递 Slice 规范的操作:string op(string sin, out string sout); ICE 中间件 仅供内部使用 第 53 页 2014 年 7 月 2 日 C++映射代码:virtual std::string op(const std::string &, std::string &,const Ice::Current & = Ice::Current()) = 0; • in 参数通过值或 const 引用传递。 • out 参数通过引用传递。 • 返回值通过值传递。 • 在 Slice 规范中["cpp:const"]指令可以指定 const 成员函数。 4.3.6 异常 异常:只抛出 Slice 异常规范中定义的异常。 抛 C++异常,客户会收到 UnknownException; 抛未在异常规范中定义的用户异常,客户会收到 UnknownUserException; 抛 Ice 运行时异常,客户会收到 UnknownLocalException。 4.3.7 激活服务者 NodePtr servant = new NodeI(name); Ice::Identity id; id.name = name; MyAdapter->add(servant, id); 1. 实例化服务者类 在堆上创建 NodeI 实现类,赋给智能指针 servant。如果要调用 NodeI 类的成员函数,应该 定义 NodeIPtr 智能指针: typedef IceUtil::Handle NodeIPtr; NodeIPtr servant = new NodeI(name); 2. 服务者对应的 Ice 对象创建标识 struct Identity { string name; string category; }; ICE 中间件 仅供内部使用 第 54 页 2014 年 7 月 2 日 通常 category 为空。只为 name 赋值。 3. 激活服务者 MyAdapter->add(servant, id); 将代表服务者的智能指针 servant,以及标识 id 添加到适配器的服务者映射表(ASM)中,从而激 活了服务者。可以接收客户端的服务了。客户端代理包含服务器寻址信息,请求的 Ice 对象标 识 id,客户调用操作时,对象标识一同发给服务器,服务器根据对象 id 查找 ASM 表,找到该 标识的服务者,就把请求分派给它的成员函数。 MyAdapter->addWithUUID(servant); 未指定标识,使用 UUID 作为对象标识,也可以使用 IceUtil::generateUUID()创建全局唯一标识, 再赋给 add 操作。 4. 创建代理 NodePrx proxy = NodePrx::uncheckedCast(MyAdapter->add(servant, id)); 现在,可以将 proxy 传给客户端使用了。 Ice 提供了直接创建代理的操作: Ice::Identity id; id.name = IceUtil::generateUUID(); ObjectPrx o = MyAdapter->createProxy(id); 无论对象标识 id 对应的服务者是否被激活,该操作都会创建的代理。 4.4 简单文件系统的设计和实现 4.4.1 系统需求 该系统为层次结构,由目录和文件组成。目录是子目录、文件的容器,顶层为根目录。同 级各个目录、文件不能同名。 该系统包含固定数目的目录、文件。不能创建、销毁目录、文件。只能一次读写文件全部 内容,不能读写部分内容,不支持路径。 ICE 中间件 仅供内部使用 第 55 页 2014 年 7 月 2 日 4.4.2 定义 Slice 文件 Filesystem.ice: module Filesystem { interface Node { idempotent string name(); }; exception GenericError { string reason; }; sequence Lines; interface File extends Node { idempotent Lines read(); idempotent void write(Lines text) throws GenericError; }; sequence NodeSeq; interface Directory extends Node { idempotent NodeSeq list(); }; }; 由 Slice 语言规范可知: • Node 为节点,提供 name 操作;文件 File 和目录 Direcotry 继承自 Node。 • File 只支持文本文件,read 不会失败,只有 write 会遇到异常。read/write 是幂等操作。 • Directory 提供了列出内容的 list 操作。返回结果为 Node 序列,序列中包含的是 Node 代 理,这里 File/Directory 的基代理,可以转型为相应的具体代理。 4.4.3 服务器端 服务器由以下文件组成: • Server.cpp :这个文件含有服务器主程序。 • FilesystemI.h:这个文件含有文件系统服务者的定义。 • FilesystemI.cpp:这个文件含有文件系统服务者的实现。 ICE 中间件 仅供内部使用 第 56 页 2014 年 7 月 2 日 4.4.3.1 主程序 Server.cpp 中的 main 函数: int main(int argc, char* argv[]) { FilesystemApp app; return app.main(argc, argv); } 定义了 FilesystemApp 作为启动类,该类继承自 Ice::Application 类,在 run 成员函数中实现服务 器。 主流程: virtual int run(int, char*[]) { // 收到中止信号时干净地终止程序 shutdownOnInterrupt(); // 创建对象适配器 Ice::ObjectAdapterPtr adapter = communicator()->createObjectAdapterWithEndpoints("SimpleFilesystem", "default -p 10000"); // 创建根目录(名为""d:\root"",没有父节点) DirectoryIPtr root = new DirectoryI(communicator(), "d:\root", 0); root->activate(adapter); // 在根目录下创建 README 文件 FileIPtr file = new FileI(communicator(), "README", root); Lines text; text.push_back("This file system contains a collection of poetry."); file->write(text); file->activate(adapter); // 在根目录下创建 Coleridge 目录 DirectoryIPtr coleridge = new DirectoryI(communicator(), "Coleridge", root); coleridge->activate(adapter); // 在 Coleridge 目录下创建 Kubla_Khan 文件 ICE 中间件 仅供内部使用 第 57 页 2014 年 7 月 2 日 file = new FileI(communicator(), "Kubla_Khan", coleridge); text.erase(text.begin(), text.end()); text.push_back("In Xanadu did Kubla Khan"); text.push_back("A stately pleasure-dome decree:"); text.push_back("Where Alph, the sacred river, ran"); text.push_back("Through caverns measureless to man"); text.push_back("Down to a sunless sea."); file->write(text); file->activate(adapter); // 所有对象已创建,现在可以接收客户端请求 adapter->activate(); // 等待完成 communicator()->waitForShutdown(); if (interrupted()) { cerr << appName() << ": received signal, shutting down" << endl; } return 0; }; 1、安装 shutdownOnInterrupt(),遇到关闭信号,就关闭程序。 2、创建对象适配器 adapter,适配器名 SimpleFilesystem,协议:缺省(TCP),端口:10000。 3、创建文件系统 文件系统结构: RootDir ↙ ↘ Coleridge README ↓ Kubla_Khan • 首先调用 new DirectoryI(communicator(), "d:\root", 0) 创建根目录"d:\root",根没有父目录, 传入 0 作为父目录句柄。返回目录指针存入 root; • 然后调用 new FileI(communicator(), "README", root)创建文件 README,它的父目录为 root;为文件添加内容; ICE 中间件 仅供内部使用 第 58 页 2014 年 7 月 2 日 • 继续调用 new DirectoryI(communicator(), "Coleridge", root)创建子目录 Coleridge; • 最后调用 new FileI(communicator(), "Kubla_Khan", coleridge)创建文件 Kubla_Khan,它的 父目录为 Coleridge;为文件添加内容; 每次创建节点后,都要调用 NodeI::activate(...),将该节点添加到适配器 adapter 中,也将节 点加入它的父目录。 4、激活适配器 调用 adapter->activate()激活适配器; 5、关闭程序 调用 communicator()->waitForShutdown() 挂起当前主函数所在线程,等待通讯器关闭;如果通讯器关闭了,就检查是否正常关闭,如果是 由于关闭信号(如 Ctrl+C)引发的关闭,打印关闭信号信息;关闭程序。 4.4.3.2 服务者类定义 slice2cpp 编译 Filesystem.ice 生成 Filesystem.h,Filesystem.cpp 映射文件,定义了文件系统接 口规范。FilesystemI.h,FilesystemI.cpp 继承了映射文件中的类并予以实现。为了避免错误,加 快生成,可以使用如下命令:slice2cpp --impl Filesystem.ice 这会同时生成如下文件: Filesystem.h Filesystem.cpp FilesystemI.h FilesystemI.cpp 这样,开发 FilesystemI 就不容易出错了。 FilesystemI.h: namespace Filesystem { class DirectoryI; //提前声明,DirectoryIPtr 引用之 typedef IceUtil::Handle DirectoryIPtr; //提前声明,NodeI,FileI 引用之 class NodeI : virtual public Node { public: virtual std::string name(const Ice::Current&); ICE 中间件 仅供内部使用 第 59 页 2014 年 7 月 2 日 NodeI(const Ice::CommunicatorPtr&, const std::string&, const DirectoryIPtr&); void activate(const Ice::ObjectAdapterPtr&); private: std::string _name; Ice::Identity _id; DirectoryIPtr _parent; NodeI(const NodeI&); // 禁止复制 void operator=(const NodeI&); // 禁止赋值 }; typedef IceUtil::Handle NodeIPtr; class FileI : virtual public File, virtual public NodeI { public: virtual Lines read(const Ice::Current&); virtual void write(const Lines&,const Ice::Current& = Ice::Current()); FileI(const Ice::CommunicatorPtr&, const std::string&, const DirectoryIPtr&); private: Lines _lines; }; typedef IceUtil::Handle FileIPtr; class DirectoryI : virtual public Directory,virtual public NodeI { public: virtual NodeSeq list(const Ice::Current&); DirectoryI(const Ice::CommunicatorPtr&, const std::string&, const DirectoryIPtr&); void addChild(const Filesystem::NodePrx&); private: Filesystem::NodeSeq _contents; }; } slice2cpp --impl 生成文件系统使用了每个类的实现继承,如下图所示: ICE 中间件 仅供内部使用 第 60 页 2014 年 7 月 2 日 1、NodeI NodeI 是具体基类,从 Node 类继承 name 操作;FileI,DirectoryI 采用了多继承。实现 NodeI, 可以由 FileI,DirectoryI 复用;如果 FileI,DirectoryI 只单继承各自的 File,Directory 类,则需要分别 实现各项公共操作——File,Directory,Node 由 slice2cpp 生成的映射类,不能直接实现公共操作。 NodeI 禁止复制构造和赋值,就会使所有继承类也禁止复制构造和赋值。 NodeI 成员_name 保存节点名称,_parent 保存父目录指针,_id 保存当前对象的标识。构造 函数:NodeI(const Ice::CommunicatorPtr&, const std::string&, const DirectoryIPtr&); 创建节点时初始化节点名称和父目录,传入通讯器,用于为服务者创建标识(当前实现中未 使用这种方法,而是使用了 UUID)。 2、FileI Lines 类型变量_lines 存储文件内容,该类型定义于 Filesystem.h 中,不用查看该文件,根据 slice 规范就应该知道 Lines 为 std::vector类型——这就需要完全掌握 Slice 到 C++映 射的规则,才能帮助你使用和开发 Ice 应用程序。 3、DirectoryI 每个目录都要存储子目录、文件列表,这里使用了 NodeSeq 类型的_contents 变量。根据 slice 规范定义 sequence NodeSeq;可知 NodeSeq 是 vector,每个元素都是指向 一个节点的代理。addChild 函数用于将子节点加入到 NodeSeq 序列中。list 函数则列出 NodeSeq 序列中的名称。 4.4.3.3 服务者类实现 #include #include using namespace std; //———————————————————————————————————— // NodeI //———————————————————————————————————— std::string Filesystem::NodeI::name(const Ice::Current&) { ICE 中间件 仅供内部使用 第 61 页 2014 年 7 月 2 日 return _name; } // NodeI 构造器 Filesystem::NodeI::NodeI(const Ice::CommunicatorPtr& communicator, const string& name, const DirectoryIPtr& parent) : _name(name), _parent(parent) { // 创建标识,根目录具有固定标识 "RootDir",非根目录使用 UUID,把标识保存到_id 成员中。 if(parent) { _id.name = IceUtil::generateUUID(); } else { _id.name = "RootDir"; } } // NodeI activate() 成员函数 // 创建节点后,调用本函数,NodeI 调用适配器的 add 操作把自身加入到 ASM 中, // 并将 add 返回的代理转化为 NodePrx 类型,调用 addChild 添加到它父节点_contents 列表中。 // 如果父节点不存在,则什么也不做。 void Filesystem::NodeI::activate(const Ice::ObjectAdapterPtr& a) { NodePrx thisNode = NodePrx::uncheckedCast(a->add(this, _id)); if(_parent) { _parent->addChild(thisNode); } } //———————————————————————————————————— ICE 中间件 仅供内部使用 第 62 页 2014 年 7 月 2 日 // FileI //———————————————————————————————————— Filesystem::Lines Filesystem::FileI::read(const Ice::Current&) { return _lines; //读取内容 } void Filesystem::FileI::write(const Filesystem::Lines& text, const Ice::Current&) { _lines = text; //写入内容 } // FileI 构造器,只是将参数简单地传给 NodeI 基类 Filesystem::FileI::FileI(const Ice::CommunicatorPtr& communicator, const string& name, const DirectoryIPtr& parent) : NodeI(communicator, name, parent) { } //———————————————————————————————————— // Directory //———————————————————————————————————— Filesystem::NodeSeq Filesystem::DirectoryI::list(const Ice::Current& c) { return _contents; } // DirectoryI 构造器,只是将参数简单地传给 NodeI 基类 Filesystem::DirectoryI::DirectoryI(const Ice::CommunicatorPtr& communicator, const string& name,const DirectoryIPtr& parent) : NodeI(communicator, name, parent) { } // addChild 由子节点调用以便将自身添加到父目录的_contents 成员 ICE 中间件 仅供内部使用 第 63 页 2014 年 7 月 2 日 void Filesystem::DirectoryI::addChild(const NodePrx& child) { _contents.push_back(child); //vector } 4.4.4 客户端 以下实现用 Ice 的较低层次的 Api,也可以从 Ice::Application 类继承也可以实现,代码也更简单。 Client.cpp 文件: #include #include #include #include using namespace std; using namespace Filesystem; // 以树形风格递归打印目录 dir 的内容。 // 对于文件,则显示每个文件的内容。 // "depth"参数是当前嵌套层次(用于缩进)。 static void listRecursive(const DirectoryPrx& dir, int depth = 0) { string indent(++depth, '\t'); //缩进 NodeSeq contents = dir->list(); //dir 目录下的所有子节点 for (NodeSeq::const_iterator i = contents.begin(); i != contents.end(); ++i) { DirectoryPrx dir = DirectoryPrx::checkedCast(*i); FilePrx file = FilePrx::uncheckedCast(*i); cout << indent << (*i)->name() << (dir ? " (directory):" : " (file):") << endl; if (dir) { listRecursive(dir, depth); ICE 中间件 仅供内部使用 第 64 页 2014 年 7 月 2 日 } else { Lines text = file->read(); for (Lines::const_iterator j = text.begin(); j != text.end(); ++j) cout << indent << "\t" << *j << endl; } } } int main(int argc, char* argv[]) { int status = 0; Ice::CommunicatorPtr ic; try { // 创建通讯器 ic = Ice::initialize(argc, argv); // 为根目录创建代理 Ice::ObjectPrx base = ic->stringToProxy("RootDir:default -p 10000"); if (!base) throw "Could not create proxy"; // 将代理向下转型为 Directory 代理 DirectoryPrx rootDir = DirectoryPrx::checkedCast(base); if (!rootDir) throw "Invalid proxy"; // 递归列出根目录的内容 cout << "Contents of root directory:" << endl; listRecursive(rootDir); } catch (const Ice::Exception & ex) { cerr << ex << endl; status = 1; } catch (const char * msg) { ICE 中间件 仅供内部使用 第 65 页 2014 年 7 月 2 日 cerr << msg << endl; status = 1; } // 清除 if (ic) ic->destroy(); return status; } 客户端文件: Filesystem.h Filesystem.cpp Client.cpp 在初始化运行时之后,客户创建一个代理,指向文件系统的根目录。端口为 "RootDir:default -p 10000",使用缺省协议(TCP)在 10000 端口处侦听。根目录的对象标识叫作 RootDir。客户把代理向下转换成 DirectoryPrx,并把这个代理传给 listRecursive,由它打印出文 件系统的内容。 listRecursive 完成了主要工作。它接收目录代理为参数,以及一个缩进层次参数(缩进每次 递归调用就增加一层,使得缩进与节点深度对应)。listRecursive 调用目录的 list 操作,并且遍历 所返回的节点序列: 1、代码调用 checkedCast,把 Node 代理窄化成 Directory 代理;并且调用 uncheckedCast,把 Node 代理窄化成 File 代理。在这两个转换中只有、而且肯定会有一个成功,所以不需要两次调 用 checkedCast:如果节点是 Directory,代理就是 checkedCast 返回的 DirectoryPrx;如果 checkedCast 失败,节点就一定是 File,只要使用 uncheckedCast 就可以正确的获得 File。一般而 言,如果向下转型肯定能成功的话,最好使用 uncheckedCast,而不是 checkedCast。 2、根据节点性质,打印文件/目录名字。 ICE 中间件 仅供内部使用 第 66 页 2014 年 7 月 2 日 3、如果是目录,代码就会递归调用 listRecursive,增加缩进层次;如果是文件,就调用文件的 read 操作,取回文件内容序列,遍历打印内容行序列。 4、根据服务器端的构造,我们可以在客户端产生如下的输出: Contents of root directory: README (file): This file system contains a collection of poetry. Coleridge (directory): Kubla_Khan (file): In Xanadu did Kubla Khan A stately pleasure-dome decree: Where Alph, the sacred river, ran Through caverns measureless to man Down to a sunless sea. 4.4.5 总结 上述实现的简单文件系统还存在的问题: 1、端点信息硬编码到程序中,可以配置到配置文件中,然后从文件中选取; 2、客户进行了一些不必要的远程调用; 3、不支持并发,两个客户同时读、写同一个文件,会产生冲突,可以通过 Ice 多线程并发机 制来解决这个问题。 ICE 中间件 仅供内部使用 第 67 页 2014 年 7 月 2 日 5 Ice 应用—高级进阶 5.1 Ice 属性和配置 待补充 5.2 C++线程和同步 待补充 5.3 Ice 流接口 5.4 异步程序设计(AMI 和 AMD) 待补充 5.5 动态调用和分派程序设计 待补充 5.6 其它(待补充) ……… ICE 中间件 仅供内部使用 第 68 页 2014 年 7 月 2 日 6 Ice 服务详解及应用 6.1 IceGrid 6.2 Freeze 与 FreezeScript 6.3 IceSSL 6.4 Glacier2 6.5 IceBox 6.6 IceStorm 6.7 IcePatch2 ICE 中间件 仅供内部使用 第 69 页 2014 年 7 月 2 日 7 Ice 功能扩充—插件模块 … … ICE 中间件 仅供内部使用 第 70 页 2014 年 7 月 2 日 8 Ice 在不同平台和语言的开发和运行环境配置 具体参考 Ice 安装包中的 ReadMe 说明文本。
还剩70页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

100200

贡献于2016-07-17

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf