什么是ICE?
网络通信引擎ICE(Internet Communications Engine)是Zero C公司的分布式系统开发专家实现的一种新的高性能的面向对象中间件平台。从根本上说, ICE 为构建面向对象的客户-服务器应用提供了工具、 API( Application Program Interface)和库支持。 [1] 基于ICE可以实现电信级的解决方案。 ——百度百科
Ice 是一种面向对象的中间件平台。从根本上说,这意味着 Ice 为构建面向对象的客户-服务器应用提供了工具、API 和库支持。Ice 应用适合在异种环境中使用:客户和服务器可以用不同的编程语言编写,可以运行在不 同的操作系统和机器架构上,并且可以使用多种网络技术进行通信。无论部署环境如何,这些应用的源码都是可移植的。
什么是面向对象的中间件?
可以将分布、异构的网络计算环境中各种分布对象有机地结合在一起的软件中间件。实现彼此之间的互操作以完成系统的快速集成。
总结:
相关术语
-
客户与服务器
客户是主动方,向服务器发送请求
服务器是被动方,接收客户请求提供服务
注意:客户也可以是服务器,服务器也可以是客户,观察一次请求中的行为来判断是什么角色
-
Ice对象
这个对象有几个特征:
- 可以相应客户请求
- 有一个主接口和0个或多个其他接口,这些其他的接口叫做facets(面),客户请求的时候就是从这些facets中挑选来访问
- 有一个对象标识,全局唯一
- 一个操作有0个或多个参数,和一个返回值,参数有名称且有方向,in参数是客户初始化传给服务器,out参数是服务器初始化,传给客户(返回值是特殊的out)
- 一个ice对象可以在一个或多个服务器中实例化,多个实例统一还是一个ice对象(?)
-
代理
代理就是我们远程调用的“中介”,我们通过代理才能联系到具体的Ice对象
代理是干嘛的:封装了一系列操作如寻找Ice对象,激活对象,in参数传入,等待操作,out参数返回给客户
代理怎么能找到 Ice对象的?
因为代理有对象的
- 寻址信息:具体的服务器信息
- 对象标识:具体对象
- 可选的facet标识符:确定请求哪个接口
-
串化代理
代理的信息如寻址信息,对象标识,facet标识符可以写成字符串的形式并存储到数据库中,这叫串化代理
我们可以自己创建代理,只要我们有对象的相关信息
-
直接代理
直接代理内部存储对象的标识,和服务器地址
客户向服务器发送请求,通过代理来直接找到具体服务器和具体对象
-
间接代理
间接代理存有对象标识和对象适配器名,并没有服务器地址信息
那怎么找到具体服务器呢?
是通过某个定位器服务(如IcePack服务),到这个服务中通过对象适配器名来找到具体服务器地址返回给客户
客户再通过此地址找到具体服务器,再通过对象标识找到具体对象(参考DNS)
-
直接绑定 vs 间接绑定
什么叫绑定:解析代理拿到服务器地址(协议-地址)
直接代理就是直接绑定:直接解析
间接代理就是间接绑定
间接绑定的好处:当服务器地址改变时,我们只需要改变定位器服务中的服务器地址即可,而且客户的代理还可以正常使用不受影响
-
Servants(中文意思:仆人,雇员)
servant存在在服务器上,执行具体请求的处理,一个servant可以对应一个Ice对象,此时ice对象标识在servant中是隐含的,也可以对应多个ice对象,此时是在具体请求处理的时候可以提现是哪一个对象
一个ice也可以对应多个servant,情况是一个ice上有多个不同的服务器地址,每个服务器上的servant体现的是同一个ice对象
-
“最多一次”语义
就是对于客户的请求,都不会出现请求两次的情况,只有在确定第一次请求失败的情况下才会重试
-
同步方法调用(Synchronous Method Invocation)
在缺省情况下,问题来了,什么是缺省情况,就是默认情况
缺省情况(默认情况)下,ice使用的请求分派模型是同步的远程过程调用
行为就像在本地调用一样,客户端请求之后就此客户线程被挂起,只有在调用完成时才会恢复,继续运行其他逻辑
说白了,就是跟调用本地方法一样,调用结束继续运行,没结束就一直卡这个方法这里
-
异步方法调用(Asynchronous Method Invocation)
ice支持异步方法调用,调用的时候除了传递需要的参数以外,还需要传递一个回调对象,然后立即返回,去干别的事,调用完成后的结果就会传递给这个回调对象(异常信息也会传递)
当然,服务器是不知道客户是同步还是异步,只是接受请求执行
-
异步方法分派(Asynchronous Method Dispatch)
这个是服务器端的异步调用,也就是这个是服务器端的AMI,就是服务器中收到请求后释放处理该请求的执行线程,然后处理完成后再返回给客户(不太懂)
当然,客户也不知道服务器使用的是同步还是异步,只是发送请求然后等结果
-
单向方法调用(Oneway Method Invocation)
就是客户发送调用请求,但是这个请求会先交给本地传输机制缓冲,然后由操作系统异步的去调用,没有返回值,尽力而为
单向调用条件:对没有返回值,没有out参数,不抛出用户异常的操作,才可以单向调用,并且只有目标对象提供了面向流的传输机制(TCP/IP,SSL)才能使用单向调用
服务器处理单向调用是无次序的,可能你先请求的后处理(由于每个调用都在自己线程中被分派,且线程调度是无法预测的)
-
成批的单向调用(Batched Oneway Method Invocation)
就是把所有单向调用请求都攒起来,一起发送,效率更高
而且,成批的单向调用发送到服务器后,会分配一个线程去处理,所以调用是有次序的
-
数据报调用(Datagram Invocations)
类似于单向调用,只不过是基于UDP协议的,比单向调用更不可靠
因为是使用UDP的原因所以,数据报调用无序(各个调用不会按次序到达),还可能丢包
-
成批的数据报调用(Batched Datagram Invocations)
也是攒起来一起发送,调用有序,但是由于丢包可能成批消息都会丢掉,不过不会出现个别消息丢掉的情况,因为是一起发送的
-
运行时异常
任何操作调用都可以引发运行时异常。 运行时异常由 Ice run time 预先 定义,涵盖了常见的出错情况,比如连接失败、连接超时,或资源分配失 败。对于应用而言,运行时异常是作为适当的 C++ 或 Java 异常给出的,并且与这些语言的原生异常处理能力完好地集成在了一起。
就是运行的时候出现异常
-
用户异常
向客户指示用户特有的出错情况
与运行时异常一样,会映射到C++和Java的原生异常
-
属性
属性在文本文件中按照 “名-值” 的形式存储,如以下方式:
Ice.Default.Protocol=tcp
可以配置比如线程池尺寸、跟踪 级别,以及各种其他的配置参数。
Slice语言简单介绍
每个 Ice 对象都有一个接口,该接口具有一些操作。接 口、操作,还有在客户及服务器间交换的数据的类型,都是用 Slice 语言定
义的
并且可以根据一些映射规则来映射成你用的语言(Java),会自动生成相应代码
客户与服务器结构
-
Ice核心
为远地通信提供了客户端和服务器端运行时支持。其中的大量代 码所涉及的是网络通信、线程、字节序,以及其他许多与网络有关的问题
简单来说就是帮我们封装好了关于通信线程这些代码
-
Ice API
Ice核心的通用部分可通过Ice API访问,例如可以管理事务,Ice run time的初始化和结束
-
代理代码
根据你的Slice定义生成,主要有以下两个功能
- 提供向下调用的接口,调用代理API中的某个函数,就会有一个RPC消息发送给服务器,调用目标对象上的对应函数
- 提供整编和解编代码,我认为就是序列化和反序列化,他让复杂的数据结构序列化然后传输,反序列化就是数据传送到了,我们需要反序列化出真正的数据
-
骨架代码
是服务器端的 “代理代码”,主要有以下功能
- 提供向上调用接口,允许Ice run time 把控制线程转交给你编写的应用代码
- 提供整编和解编代码,所以服务器也可以接受参数并返回参数和异常等信息
-
对象适配器(只有服务器才使用对象适配器)
是专用于服务器端Ice API的一部分,只有服务器才使用对象适配器,对象适配器有以下功能
- 把客户的请求映射到编程语言对象的特定方法
- 对象适配器与一个或多个传输端点关联在一起,就是可以同时连TCP/IP端和UDP端
- 对象适配器要负责创建可以传给客户的代理,对象适配器知道它的每 个对象的类型、标识,以及传输机制的详细情况,并且会在服务器端应用代码要求创建代理时在其中嵌入正确的信息。
Ice协议
Ice提供了一种RPC协议,可以把TCP/IP作为底层传输机制,也可以把UDP作为底层传输机制,如果涉及打客户与服务器之间加密通信,也可以把SSL用作传输机制
Ice协议都定义了以下几点:
- 消息类型:如请求和答复类型
- 协议状态机:确定客户与服务器以怎样的序列交换不同的消息类型,还包括相关的TCP/IP连接建立和关闭语义
- 编码规则:确定在线路上怎样表示数据的类型
- 每种消息类型的头,都有以下几点:消息类型、消息尺寸、所使用协议及编码版本
Ice支持在线路上进行压缩(通过配置一个参数就行),线路压缩有什么好处呢?数据被压缩节省带宽,尤其是对于交换大量数据的场景
Ice协议适用于高效的事件转发:转发消息不必解编或整编,直接转发就行
Ice协议适用构建双向操作:如果服务器想把一条消息发送给客户的某个回调对象,这个回调对象可以通过客户的原来创建的连接传给服务器,如果客户被防火墙挡着呢,服务器肯定是不能主动建立连接了,所以此时双向操作就很重要了
对象持久
Ice有内建的对象持久服务叫Freeze,默认数据库是Berkeley DB,也可以指定其他的详解21章
关于数据库迁移,22章
Ice服务
IcePack(Ice中的DNS)
Ice定位服务,用于间接代理,解析时解析对象适配器名来找到对象的地址,除了这个主要功能之外还有以下功能:
- 允许注册服务器,并且第一个客户请求到达时自动启动服务器
- 支持部署描述符(deployment descriptors),能让你轻松地配置 含有若干服务器的复杂应用。(应该意思就是服务器多了也会配置的很方便)
- 找对象:提供了简单的对象查找服务,客户可以通过这个来获取想要的对象的代理
IceBox(?应用服务器)
是一种应用服务器,可用于协调许多应用组件的启动和停 止。应用组件可以作为动态库、而不是进程进行部署。例如,你可以在单 个 Java 虚拟机中运行若干应用组件,而无需使用多个拥有自己的虚拟机的进程,从而减轻整个系统的负担。
IceStorm(Ice中的消息队列)
是一种发布-订阅服务,主要解除客户与服务器耦合,还可以多实例分布到其他机器上运行,对诸多请求进行负载均衡
IcePatch
软件修补服务器,客户可以连接IcePatch,说要获取哪个应用的更新,IcePatch就会自动检查客户软件的版本,并且压缩的形式下载客户的更新包,节省带宽
你可以用 Glacier 服务来保护软件补丁,只让得到 授权的客户下载软件补丁
Glacier(Ice防火墙)
是Ice的防火墙服务,保证客户与服务器安全通信
Glacier 支持相互认证,以及安全的会话 管理。
Ice应用编写步骤
- 编写Slice定义并编译
- 编写服务器并编译
- 编写客户并编译
Slice语言
4.8和4.9仔细阅读
-
文件命名:必须以.ice结尾,后缀必须小写
-
文件格式:对代码编排没有要求
-
预处理:支持#ifndef、#define、#endif,以及#include(?)
-
定义次序:如模块、接口或者类型定义,次序没要求,但是必须声明这是什么
-
语法规则:
-
注释:可以使用C或C++风格的注释
/* * C-style comment. */
// C++-style comment extending to the end of this line.
-
关键字:一般关键字必须小写,除了两个例外:Object 和LocalObject,详情见附录A
-
标识符:以字母开始,后面可以跟字母或数字,注意:不能有下划线
-
标识符大小写不敏感,但是在同一作用域下命名应该统一,不能出现TimeOfDay 和TIMEOFDAY两种方式
-
关键字是其他语言的关键字,但是不是Sclice的语言的关键字,用这个关键字也可以作为标识符,但是映射到具体的语言后会加一个前缀 “_” 【不推荐使用其他语言的关键字作为标识符】
-
Slice关键字前加 ‘\’ 反斜线可以被识别为不是标识符,这个是为了解决以前写的代码,Slice更新后成为了关键字的情况【不推荐使用】
-
保留的标识符:以Ice开头的任何标识符不允许出现,以下面任何一种后缀结尾的 Slice 标识符也是保留的:Helper、 Holder、Prx,以及Ptr。因为Java 和 C++ 语言映射使用了这些后缀,保留它们是 为了防止在生成的代码中发生冲突。
-
-
基本类型
- 整数类型:提供short,int,long,注意:Slice没有提供无符号类型,为什么不提供?因为无符号类型难以映射到没有原生无符号类型的语言如Java,说白了就是不好映射,而且无符号类型价值也不大,所以就不提供了
- 浮点类型:float,double,这些类型遵循 IEEE 的单精度和双精度浮点表示规范,如果某种实现 不支持 IEEE 格式的浮点值,Ice run time 会把这样的值转换为原生的浮点表示(取决于原生浮点格式的容量,可能会损失精度,甚至是数量级)
- 串:string,不能出现0(为了C++),没有null串的概念(python没有null串概念),如果想用null串,可以用类,串的序列或空串(empty string)来表示null串的概念
- 布尔值:bool,false和true两种值
- 字节:byte,地址空间之间传送时能保证不会改变,这一保证允许你交换二进制数据
用户定义的类型
用户可定义:
- 枚举(enumerations)
- 结构(structures)
- 序列(sequences)
- 词典(dictionaries)
枚举
枚举定义:
enum Fruit { Apple, Pear, Orange };
注意:Ice中枚举定义中的枚举符不能有值,但是能保证是有顺序值的,就是Apple的值一定比pear的值小
enum Fruit { Apple = 0, Pear = 7, Orange = 2 }; // Syntax error
注意:定义之后的枚举符也会进入一个命名空间,不能重复定义,即使是不同的枚举
enum Fruit { Apple, Pear, Orange };
enum ComputerBrands { Apple, IBM, Sun, HP }; // Apple redefined
注意:Slice不允许定义空的枚举
实际使用中,我们一般只用枚举符来传递,到对应服务器就会自己翻译成对应值,而不是传送值来表示对应枚举符,因为可能到对面之后值就是表示另一个枚举符了,当我们用0表示Apple时,我们只传Apple,不传0
结构(可以理解为Java中的类)
如何用Ice定义一个结构呢?参考如下代码
struct TimeOfDay {
short hour;
short minute;
short second;
};
这样就有了一个TimeOfDay的新类型了
注意:结构不能嵌套定义,也就是一个结构里面不能再定义一个结构,结构不能为空,结构不支持继承
序列(参考List)
序列可以理解为一个集合,可以为空,也可以不包含元素,也可以包含任意元素,同时,序列也可以包含序列
序列定义方式如下代码:
sequence<Fruit> FruitPlatter;
// 序列嵌套序列
sequence<FruitPlatter> FruitBanquet;
序列可用于构建许多种 collection,比如向量、列表、队列、集合、包 (bag),或是树(次序是否重要要由应用决定;如果无视次序,序列充当的就是集合和包)。
词典(键值对Map)
就是键值对的映射,具体代码如下:
struct Employee {
long number;
string firstName;
string lastName;
};
dictionary<long, Employee> EmployeeMap;
词典的值类型可以是用户定义的任何类型。但词典的键类型只能是以下 类型之一:
- 整型(byte、short、int、long、bool,以及枚举类型)
- string
- 元素类型为整型或string 的序列
- 数据成员的类型只有整型或string 的结构
常量定义与直接量
常量定义的类型必须是以下类型中的一种:
- 整型、bool、byte、short、int、long或枚举类型
- float或double
- string
几个常量定义的例子:
const bool AppendByDefault = true;
const byte LowerNibble = 0x0f;
const string Advice = "Don't Panic!";
const short TheAnswer = 42;
const double PI = 3.1416;
// 枚举类型常量定义
enum Fruit { Apple, Pear, Orange };
const Fruit FavoriteFruit = Pear;
直接量和Java差不多,有一些小的例外
-
布尔常量只能用false和true初始化,不能用0或1来表示false或true
-
和C++一样,你可以直接用十进制,八进制,或十六进制来指定整数的直接量
例如:
const byte TheAnswer = 42; const byte TheAnswerInOctal = 052; const byte TheAnswerInHex = 0x2A; // or 0x2a
注意:如果把byte解释为数字而不是位模式,在不同语言可能会得到不同的结果,如C++中byte会映射到char
注意:用于指示长常量的后缀 “l” 或 “L” 是非法的
const long WrongToo = 1000000L; // Syntax error
但是表示float的 “f” 和 “F” 后缀是合法的
-
整数直接量,浮点直接量必须是在其常量类型的范围内
-
串直接量支持与C++相同的转义序列
-
接口、操作,及异常
接口
Slice中的接口定义可以参考Java中的接口定义方式,但是Slice中的接口不允许重载
以下是定义方式:
struct TimeOfDay {
short hour;
short minute;
short second;
};
// 接口定义
interface Clock {
TimeOfDay getTime();
void setTime(TimeOfDay time);
};
操作(类似接口中的方法)
操作定义必须包含返回类型,以及零个或更多参数定义。
一个操作可以有一个或多个输入参数
注意:不能省略参数类型的定义,如TimeOfDay
interface CircadianRhythm {
void setSleepPeriod(TimeOfDay startTime, TimeOfDay stopTime);
// ...
};
也可以在操作中定义输出参数
void getTime(out TimeOfDay time);
注意:如果既有输出参数,又有输入参数,输出参数应该放在输入参数后面
// OK TimeOfDay stopTime,
void changeSleepPeriod( TimeOfDay startTime,
out TimeOfDay prevStartTime,out TimeOfDay prevStopTime);
操作定义的风格:
对于只有一个返回值的情况,常见做法就是给操作写返回值,而不是写一个out参数
对于有多个返回值的情况,常见的做法是返回值为void,其他是out参数,如果有一个特别重要的返回值,就返回这个值,其他为out参数
再次注意:操作不支持重载!
-
Nonmutating 操作
此关键字表示:该操作不会改变它的对象状态
用途一: 映射的时候会映射到C++ const成员函数
用途二:由于Ice run time遵循最多一次语义,所以第一次请求发送到服务器,途中断开了,Ice不会再次请求,因为它不知道自己是否请求成功了,此时如果加上了Nonmutating 关键字,Ice就会发出第一次尝试,因为它知道对象状态不会改变,如果第一次尝试也失败了,第二次尝试就会报告错误了,也可以在配置文件中设置失败的重连次数
-
Idempotent 操作
此关键字表示:如果对某个操作进行两次连续的调用,其效果与一次调用是一样的,这 个操作就是 idempotent 操作。
场景就是赋值:x = 1,就算做n次尝试,也是x = 1
idempotent 关键字表明某个操作可以安全地多次执行
一个操作可以有nonmutating 修饰符,也可以有idempotent 修饰符, 但不能同时有这两个修饰符(nonmutating 隐含了idempotent)
具体代码:
interface Clock {
nonmutating TimeOfDay getTime();
idempotent void setTime(TimeOfDay time);
};
用户异常
异常与结构类似,但是异常可以为空,如下是异常的定义示例代码:
exception Error {}; // Empty exceptions are legal
exception RangeError {
TimeOfDay errorTime;
TimeOfDay minTime;
TimeOfDay maxTime;
};
异常允许你向客户返回任意数量的出错信息,操作可以使用异常规范来说明可能会有异常返回给客户,下面是异常的使用:
interface Clock {
nonmutating TimeOfDay getTime();
idempotent void setTime(TimeOfDay time) throws RangeError, Error;
};
如果某个操作实现在运行时抛出的异常没有在异常规范中列出,客户就会收到一个运行时异常
异常继承(不太了解)
注意:异常只能单继承(多继承难以映射到许多编程语言)
异常继承示例代码:
exception ErrorBase {
string reason;
};
enum RTError { DivideByZero, NegativeRoot, IllegalNull};
exception RuntimeError extends ErrorBase {
RTError err;
};
enum LError { ValueOutOfRange, ValuesInconsistent};
exception LogicError extends ErrorBase {
LError err;
};
exception RangeError extends LogicError {
TimeOfDay errorTime;
TimeOfDay minTime;
TimeOfDay maxTime;
};
ErrorBase是根部异常,包含解释原因的串,RuntimeError和LogicError是从ErrorBase派生的,各自有一个枚举来划分错误的范畴
LogicError又派生出了RangeError,用于报告错误详情
抛异常的时候只需要抛出ErrorBase即可,如果有特定异常就会抛出下面的特定异常
Ice运行时异常
注意:所有的操作都会默认抛出运行时异常,所以你在操作后面抛出的时候就不能再写运行时异常了
本地异常 vs 远程异常
大多数出错情况都会在客户端检测到。例如,如果联系服务器的尝试失 败,客户端 run time 就会引发ConnectTimeoutException。但有三种特定的 异常情况会由服务器检测到,这些异常会通过 Ice协议显式地告诉客户端 run time,下面是这三种特定异常:
-
ObjectNotExistException(没有对应对象)
表示,请求到达了服务器,但是没有具体的一个对象去处理
ObjectNotExistException 是一份死亡证明书:它表明目标对象在服 务器中不存在,而且,也不会存在。如果你收到这个异常,你应该清理你分配过的、与这个对象有关的所有资源。
-
FacetNotExistException(没有对应接口)
客户试图联系某个对象的一个facet(就是某个接口),但是这个接口不存在
-
OperationNotExistException(没有对应方法)
服务器可以定位到一个对象,但是发现这个对象竟然没有这个操作,就会引发此异常,以下两种情况会导致此异常:
- 你对类型不正确的代理做了不进行检查的向下转换
- 在构建客户和服务器时,使用了不一致的 Slice 接口定义,也就是说, 客户构建时使用的对象接口定义表明某个操作存在,而服务器构建时使用了另外一种版本的接口定义,其中没有这个操作。
在服务器出现的错误不能用以上三种情况来描述的话,就会作为两种一般异常之一告诉客户:
-
UnknownUserException(服务器捕捉到了异常但是你在操作中没有声明)
这个异常表明,某个操作实现抛出的异常没有在操作的异常规范中 声明。比如一个 ClassCastException, 客户就会收到UnknownUserException。
-
UnknownLocalException(服务器也没捕捉到异常,出现了运行时异常)
如果遇到了除以上三种之外的异常,就会回复一个这个表示服务器遇到了运行时异常
收到这个UnknownLocalException的根本原因是,服务器也没有捕捉并处理所有异常,也是遇到了运行时异常
其他的运行时异常都由客户端run time负责检测,在本地引发
接口语义与代理
先看以下接口定义:
interface WorldTime {
idempotent void addZone(string zoneName, Clock* zoneClock);
void removeZone(string zoneName) throws BadZoneName;
nonmutating Clock* findZone(string zoneName) throws BadZoneName;
nonmutating TimeMap listZones(); idempotent void setZones(TimeMap zones);
};
上述的Clock*,就是代理,可以把它看做是指针,指向的是具体的对象,以下是代理的几个特点
- 代理可以为null
- 代理可以悬空(指向的对象已经不存在)
- 通过代理分派的操作使用的是迟后绑定:如果与代理的类型相比,代理 所代表的对象的实际运行时类型派生层次更深,调用的就将是派生层次最深的接口的实现。(意思就是如果代理代表的对象还有具体处理的对象,那么就继续往下调用最具体的接口)
接口继承
注意:接口可以多继承
接口继承后既有父接口的操作,也有自己的操作
注意:接口继承中,操作名字不能重复
注意:所有的Slice接口都会默认的继承Object,但是继承Object是隐含的,不能显示的写出来,如下代码就是不正确的
interface MyInterface extends Object { /* ... */ }; // Error!
自引用的接口
代理具有指针语义,所以我们可以定义自引用的接口。例如:
interface Link {
nonmutating SomeType getValue();
nonmutating Link* next();
};
Link接口含有一个next操作,这个操作返回的是一个指向Link接口的代 理。显然,这可以用来创建接口链;接口链中最后的链接的next 操作返回null 代理。
空接口(没看懂)
Slice中接口允许定义为空,但是当定义一个空接口的时候先想一下原因,如果不得不定义空接口,那么会失去以下能力:
改变对象模型在物理的服务器进程上的分布方式,因为无法把共享了隐藏状态的接口分置在不同的地址空间中
接口继承 vs 实现继承
接口继承只适用于接口,并不意味着这些接口的实现也要发生继承关系,可以在实现接口时选择使用实现继承
总而言之, Slice 继承只是在建立类型兼容性。它并没有说出任何与接 口的实现方式有关的事情,因此,你可以针对你的应用的实际情况来选择实现方式。
类
类时接口和结构的组合体,既可以有操作,还可以有数据成员
类支持继承,类允许你在客户端实现行为,而接口只允许在服务端实现行为
简单类
前提:结构可以做的事情,就不要用类了,为了性能考虑,结构能用的地方,都能使用类,但是与结构不同,类可以为空
类定义示例代码:
class TimeOfDay {
short hour;
short minute;
short second;
};
// 类可以为空,结构不能为空
class EmptyClass {}; // OK
struct EmptyStruct {}; // Error
但是,使用空的类的时候停下来想想,为什么要使用
类继承
类与结构不同,类可以继承,但是,类是单继承的
当结构需要继承时,我们就用到了类,类可以通过继承扩展,例如我们原来是TimeOfDay,我们突然又想加一个日期,此时用到了继承相关的,具体示例代码如下:
class TimeOfDay {
short hour;
short minute;
short second;
};
class DateTime extends TimeOfDay {
short day;
short month;
short year;
};
子类也不能重新定义父类的数据成员
class Base {
int integer;
};
class Derived extends Base {
int integer; // Error, integer redefined
};
类继承语义
父类作为参数的地方,子类对象可以传入,但是如果对方没有子类相关的定义的话,子类多出来的信息就会丢掉,只保留父类相关信息
类用作联合
class UnionDiscriminator {
int d;
};
class Member1 extends UnionDiscriminator {
//d==1
string s;
float f;
};
class Member2 extends UnionDiscriminator {
//d==2
byte b;
int i;
};
可以通过d的值来判断具体是哪个member
自引用的类
class Link {
SomeType value;
Link next;
};
注意此处的next包括的是类的值
类 VS 结构
- 类支持继承
- 类可以自引用
- 类可以有操作
- 类可以实现接口
一般情况来说,结构是值得简单集合,我们需要类的强大特性时再用类,用结构可以有更好的性能
说白了,就是能用结构就用结构,别直接用类
有操作的类
类中也可以像接口一样定义操作,语法也与接口相同
但是,类中的操作并不会产生远程调用,但是又有如下问题:
如果客户从服务器那里接收到一个有操作的 类,但客户和服务器是用不同的语言实现的,会发生什么事情?有操作的 类要求接收者提供类实例工厂。 Ice run time 只整编类的数据成员。如果类 有操作,类的接收者必须提供一个类工厂,在接收者的地址空间里实例化
这个类,接收者还要负责提供类的操作的实现。
因此,如果你使用了有操作的类,客户和服务器应该能各自访问这个类 的操作的一种实现。
使用有操作的类要慎重
实现接口的类
Slice 类也可以用作服务器中的 servant,也就是说,类的实例可以用来 提供接口的行为,例如:
interface Time {
nonmutating TimeOfDay getTime();
idempotent void setTime(TimeOfDay time);
};
class Clock implements Time {
TimeOfDay time;
};
一个类也可以实现多个接口
类继承的局限性
和接口继承一样,类不能重新定义继承来的数据成员或操作
传值 vs 传引用
传值:把类发送给接受者,接受者对类的数据成员做变动,不会影响到发送者的类
传引用:看代码:(不太懂)
class TimeOfDay {
short hour;
short minute;
short second;
string format();
};
interface Example {
TimeOfDay* get(); // Note: returns a proxy!
};
此处的TimeOfDay*,返回的是TimeOfDay类的代理,客户可以通过这个代理访问操作,但是不能访问数据成员
提前声明
接口和类都可以进行提前声明。提前声明能够让你创建相互依赖的对 象,例如:
interface Child;
sequence<Child*> Children;
interface Parent {
Children getChildren();
};
interface Child {
Parent* getMother();
Parent* getFather();
};
提前声明的接口和类可以用作结构、异 常,或类成员的类型,用作词典的值类型,用作操作的参数和返回类型。
但是,在编译器见到提前声明的接口或类的定义之前,你不能继承它们
模块
为了避免名字冲突,可以分模块
module MutableRealms {
module WishClient {
// Definitions here...
};
module WishServer {
// Definitions here...
};
};
不同的模块可以分布在不同的源文件(ice文件)中
如果你想在这个模块中用另一个模块里的东西怎么办?
作用域限定操作符:: 能让你引用非局部作用域中的类型
具体代码:
module Types {
sequence<long> LongSeq;
};
module MyApp {
sequence<Types::LongSeq> NumberTree;
};
我们这里成功引用了Types模块中的LongSeq
前置的::表示全局作用域,这里也可以使用 ::Types::LongSeq来引用这个LongSeq
类型ID
类型ID就是 两个冒号起头,后面跟模块及模块嵌套,再跟具体的类型名字,各个组成部分使用两个冒号分割
Object上的操作
如果Object可以有一个合法的Slice定义,那么他看起来会是这样的
sequence<string> StrSeq;
interface Object {
void ice_ping();
bool ice_isA(string typeID);
string ice_id();
StrSeq ice_ids();
// ...
};
注意Object中的操作都使用了下划线,这个是为了所有用户自己创建的类型都会隐含的继承Object,用户可以随便给操作命名而不会和Object中的重复,所以也规定了用户的命名不能有下划线
下面详细介绍这几个常用的内建操作(以下操作所有接口都支持,因为都是隐含的继承了Object):
-
void ice_ping()
用来测试某个对象是否可以到达,如果对象存在,而且消息可以成功分派给他,就成功返回
如果对象不存在,就会抛出一个运行时异常,并说明失败原因
-
bool ice_isA(string typeID)
测试目标对象是否支持指定类型,这个参数就是一个类型标识符(ice_id操作返回的类型标识符)
如果类型ID是一个子类,那么肯定可以支持它的父类类型,同样的,所有的接口都支持Object
-
string ice_id()
返回此接口派生层次最深的类型ID
-
StrSeq ice_ids()
返回此接口支持的所有类型的ID序列
本地类型
就是使用local关键字修饰的类型,例如
module Ice {
local interface ObjectAdapter {
// ...
};
};
大部分都是Slice的库提供的API
local修饰的东西不会被整编,也就意味着不能从远程访问
本地接口和本地类也没有继承Ice::Object,有一套自己的继承体系
一般情况下很少定义本地类型,但是servant 定位器必须作为本地对象实现(参 见 16.6 节)。
名字和作用域
下列成分都有自己的作用域
- 全局(文件)作用域
- 模块
- 接口
- 类
- 结构
- 异常
- 枚举
- 参数列表
在一个作用域中,标识符必须唯一,上面唯一有点难理解的就是参数列表,其实也不难理解,就是一个操作中定义若干个参数,这几个参数形成了作用域,从而不能重复,参考代码如下(p 重复):
interface Bad {
void op(int p, string p); // Error!
};
注意:Slice大小写不敏感,但是同一作用域下必须统一名字,只有大小写不同也会被认为错误
注意:外层作用域中定义过的名字,内层作用域也可以重复定义,引用外层的时候使用ID的形式即可,但是尽量避免这样写,代码如下:
module Outer {
sequence<string> Seq;
module Inner {
sequence<short> Seq;
struct Confusing {
Seq
};
};
};
注意:名字搜索顺序是,先从当前作用域查找,再从外围,再外围直到全局作用域
元数据
元数据不是Slice的组成部分,就说提供一些辅助性的信息,并且指示代码生成器以特定方式去生成代码
查看如下示例代码:
["java:type:java.util.LinkedList"] sequence <int> IntSeq;
就是告诉代码生成器生成代码的时候映射到链表,而不是数组(Slice中的序列默认映射到数组)
局部元数据定义格式:在一 对方括号中,含有一个或多个由逗号分隔的串直接量
全局元数据指令:两个方括号中,如以下指令告诉代码生成器把生成的源代码都放在com.acme包下
[["java:package:com.acme"]]
Slice编译器
就是把Slice定义映射成具体的编程语言
命令行语法
<compiler-name> [options] file…
编译器名字+指令+文件名
对于Java编译器名字就是:slice2java
可以同时编译多个文件,空格分开就行
后面的file就是以.ice结尾的文件
客户端的Slice to Java映射
书上本章内容大部分是参考资料,需要时可以参考特定的内容
但是要详细阅读8.9-8.13
定义
客户端的Slice to Java映射定义是:怎样把Slice数据类型翻译成Java类型,客户怎样调用操作、传递参数、处理错误。
标识符映射
就是映射到相同的标识符,Slice中的Clock到Java中还是Clock
注意:如果Slice中的标识符是Java中的关键字,映射后会在标识符前面加下划线(避免使用Java关键字作为标识符)
模块的映射
Slice的模块会映射为java的包
模块的嵌套会被映射为包和子包
看代码:
module M1 {
// Definitions for M1 here...
module M2 {
// Definitions for M2 here...
}
};
module M1 { // Reopen M1
// More definitions for M1 here...
};
// 映射之后
package M1;
// Definitions for M1 here...
package M1.M2;
// Definitions for M2 here...
package M1;
// Definitions for M1 here...
基本类型的映射
这个没啥说的,就是一一对应,bool成了boolean,string首字母在Java中需要大写,其他都是直接映射
用户自定义类型的映射
枚举映射
书中由于版本比较低的原因,说Java中没有枚举类
网上查了Java从1.5开始就有枚举了,所以写了个测试查看枚举映射过去后的样子
就会映射成对应Java的枚举类
// Slice定义
enum Fruit { Apple, Pear, Orange };
// 映射后的内容
public enum Fruit implements java.io.Serializable
{
Apple,
Pear,
Orange;
public void
__write(IceInternal.BasicStream __os)
{
__os.writeByte((byte)ordinal());
}
public static Fruit
__read(IceInternal.BasicStream __is)
{
int __v = __is.readByte(3);
return values()[__v];
}
}
结构映射
结构会映射到同名的Java类
Java类会有属性、无参构造、带参构造
并且会被重写hashCode和equals方法
还会重写clone方法,默认是调用Object类的clone方法,也就是默认浅拷贝
序列的映射
序列默认映射到数组,但是自己也可以改变序列的映射方式,如以下代码就能映射到链表
["java:type:java.util.LinkedList"] sequence<Fruit> FruitPlatter;
词典的映射
词典映射为map
常量的映射
常量会映射成一个java接口,里面只有一个常量值
如以下代码所示:
const double PI = 3.1416;
// 映射之后
public interface PI
{
double value = 3.1416;
}
以下到8.9了,是重点详细阅读部分
异常的映射【重点】
每一个Slice异常会映射到一个同名的Java类,类中有一个public的数据成员,还有一个ice_name方法,返回的是异常的名字
示例代码:
// Slice定义
exception GenericError { string reason;
};
exception BadTimeVal extends GenericError {};
exception BadZoneName extends GenericError {};
// 映射之后(还有其他函数没给出,是供Java映射内部使用,应用代码不应该调用)
public class GenericError extends Ice.UserException {
public String reason;
public String ice_name() {
return "GenericError";
}
}
public class BadTimeVal extends GenericError {
public String ice_name() {
return "BadTimeVal";
}
}
public class BadZoneName extends GenericError {
public String ice_name() {
return "BadZoneName";
}
}
运行时异常的映射【重点】
接口的映射【重点】
Slice接口映射到客户端的代理,一个代理就是一个Java接口
对于每个Slice接口会创建以下文件
-
【interfaceName】.java
声明Slice接口对应的Java接口
-
【interfaceName】Holder.java
为接口定义hoder
-
【interfaceName】Prx.java
在客户的接口空间中,【interfaceName】Prx的的实例就是远程服务器对应接口的实例的“本地大使”,叫代理实例
与服务器端对象有 关的所有细节,比如其地址、所用协议、对象标识,都封装在该实例中
-
【interfaceName】PrxHelper.java
有两个方法挺重要
public final class SimplePrxHelper extends Ice.ObjectPrxHelper implements SimplePrx { public static SimplePrx checkedCast(Ice.ObjectPrx b) { // ... } public static SimplePrx uncheckedCast(Ice.ObjectPrx b) { // ... } // ... }
checkedCast:向下转换,会检查传入的代理是否是本类型对象的代理,并且联系服务器进行验证,只有联系一下子看看能不能找到对应对象,才知道是不是本类型对象的代理,如果验证通过就会返回SimplePrx,不通过就会返回空
uncheckedCast:不会联系服务器,会无条件的向下转换给你需要的类型返回
-
【interfaceName】PrxHolder.java
-
【interfaceName】Operations.java
操作与Slice接口的操作相对应
以上文件是与客户端相关的代码,下面文件是与服务器端相关的:
-
【interfaceName】Disp.java
服务器端骨架类的定义
-
【interfaceName】Del.java
-
【interfaceName】DelD.java
-
【interfaceName】DelM.java
Ice.ObjectPrx接口
所有的代理都继承自这个接口,这个接口有以下几个方法要知道
package Ice;
public interface ObjectPrx {
boolean equals(java.lang.Object r);
Identity ice_getIdentity();
int ice_hash();
boolean ice_isA(String __id);
String ice_id();
void ice_ping(); // ...
}
-
boolean equals(java.lang.Object r)
比较两个代理是否相等,这个会比较代理的所有方面,比如端口也会比较
两个代理不同,不一定说明两个代理代表的对象不同,因为他们可能是传输的端口不同
-
Identity ice_getIdentity()
返回代理所代表的对象的标识,比较两个代理是否代表同一个对象会用到,就是获取他们代表对象的标识然后比较就行了
-
int ice_hash()
返回代理的哈希值
-
boolean ice_isA(String __id)
确定代理代表的对象是否支持特定接口,这个_id,是类型ID
-
String ice_id()
返回代理所代表的对象的类型ID
-
void ice_ping()
测试是否可以正常联系到对象,不能正常联系的话就会抛异常
操作的映射
接口的每个操作,代理类都有一个对应的同名函数,调用某个操作,就通过具体代理来调用即可
in参数
代理类调用可以直接传值也可以创建一个变量赋值再传变量
out参数
因为Java没有引用传递,只有值传递,为了能让out参数正常使用,提供了一个holder类,通过传递holder类的实例,来获取out参数
一旦方法调用完成,holder类实例中的值就会有了
holer类类似于下面这样,提供一个有参和无参构造,还有一个数据成员,类似于实体类了
public final class IntHolder {
public IntHolder() {};
public IntHolder(int value) {
this.value = value;
}
public int value;
}
参数类型失配
就是要求接受一个map参数,人家原来方法里规定的是map<string, long>
你在代理调用的时候给人家传了个map<long, String> 的这样一个map,就会抛出ClassCastException
null参数(没看太明白)
- 对于代理,null的话就是谁也不指向,不指向具体对象
- 序列和词典不能为 null,但可以是空的。为了让这些类型的使用更容 易,只要你把 Java null 引用用作参数、或序列或词典类型的值,Ice run time 都会自动把空的序列或词典发给接收者。
- Java串可以为null,但Slice串不能(因为Slice串不支持null串的概念)。 只要你把 Java null 串用作参数或返回值,Ice run time 都会自动把空串发给接收者。
什么意思:自动发送空的给接收者?
异常处理
Slice异常是作为Java异常抛出的,异常处理应该只对操作调用捕捉特定异常
注意:操作抛出异常的时候,out参数的状态是没办法保证的,可能值已经变了,也可能被改变了,这个Ice run time是没办法保证的
类的映射
Slice类是会映射到同名的Java类中的
Slice类中的数据成员会映射到Java类中的public数据成员
Slice类中的操作:编译器生成了一个叫作 XXXOperations 的接口。对于类中的 每一个 Slice 操作,在这个 XXXOperations 接口中
都有一个对应的方法。
具体代码:
// Slice定义
class TimeOfDay {
short hour;
short minute;
short second;
string format();
};
// 映射之后
public interface TimeOfDayOperations {
// Slice类中的操作会映射到这个借口中的同名方法
String format(Ice.Current current);
}
public abstract class TimeOfDay extends Ice.ObjectImpl implements TimeOfDayOperations {
public short hour;
public short minute;
public short second;
// ...
}
注意:如果Slice类中没有操作,只有数据成员的话,就会像结构一样,生成一个非抽象类
类工厂
生成的代码类图分析
a
public final class IntHolder {
public IntHolder() {};
public IntHolder(int value) {
this.value = value;
}
public int value;
}
##### 参数类型失配
就是要求接受一个map参数,人家原来方法里规定的是map<string, long>
你在代理调用的时候给人家传了个map<long, String> 的这样一个map,就会抛出ClassCastException
##### null参数(没看太明白)
- 对于代理,null的话就是谁也不指向,不指向具体对象
- 序列和词典不能为 null,但可以是空的。为了让这些类型的使用更容 易,只要你把 Java null 引用用作参数、或序列或词典类型的值,Ice run time 都会自动把空的序列或词典发给接收者。
- Java串可以为null,但Slice串不能(因为Slice串不支持null串的概念)。 只要你把 Java null 串用作参数或返回值,Ice run time 都会自动把空串发给接收者。
什么意思:自动发送空的给接收者?
#### 异常处理
Slice异常是作为Java异常抛出的,异常处理应该只对操作调用捕捉特定异常
注意:操作抛出异常的时候,out参数的状态是没办法保证的,可能值已经变了,也可能被改变了,这个Ice run time是没办法保证的
#### 类的映射
Slice类是会映射到同名的Java类中的
Slice类中的数据成员会映射到Java类中的public数据成员
Slice类中的操作:编译器生成了一个叫作 XXXOperations 的接口。对于类中的 每一个 Slice 操作,在这个 XXXOperations 接口中
都有一个对应的方法。
具体代码:
```java
// Slice定义
class TimeOfDay {
short hour;
short minute;
short second;
string format();
};
// 映射之后
public interface TimeOfDayOperations {
// Slice类中的操作会映射到这个借口中的同名方法
String format(Ice.Current current);
}
public abstract class TimeOfDay extends Ice.ObjectImpl implements TimeOfDayOperations {
public short hour;
public short minute;
public short second;
// ...
}
注意:如果Slice类中没有操作,只有数据成员的话,就会像结构一样,生成一个非抽象类