接口 Interfaces
Slice的中心焦点是定义接口
这里定义了一个称为Clock的接口类型。该接口支持两个操作:getTime和setTime。客户机通过在对象的代理上调用操作来访问支持时钟接口的对象:要读取当前时间,客户端调用getTime操作;要设置当前时间,客户端调用setTime操作,传递类型TimeOfDay的参数。
在代理上调用操作会指示Ice运行期间向目标对象发送消息,目标对象可以在另一个地址空间中,或者可以与调用者并列(在同一进程中) - 目标对象的位置对客户端是透明的。 如果目标对象位于另一个(可能是远程的)地址空间中,则Ice运行时间将通过远程过程调用调用该操作; 如果目标与客户端并置,则Ice运行时间会绕过网络堆栈,从而更有效地传递请求。
操作 Operations
参数和返回值
操作定义必须包含返回类型和零个或多个参数定义。 例如,在Clock接口中,getTime方法的返回类型为TimeOfDay,setTime方法的返回类型为void。 您必须使用void来表示操作不返回值 - Slice操作没有默认返回类型。
操作可以具有一个或多个输入参数。 例如,setTime接受一个名为Time的TimeOfDay类型的单个输入参数。 当然,您可以使用多个输入参数:
请注意,参数名称(如Java)是必需的。 您不能省略参数名称,因此下面这样是错误的:
默认情况下,参数从客户端发送到服务器,也就是它们是输入参数。 要将值从服务器传递给客户端,可以使用由out关键字指示的输出参数。 例如,在Clock接口中定义getTime操作的另一种方法是:
这实现了相同的事情,但使用输出参数而不是返回值。 与输入参数一样,您可以使用多个输出参数:
如果您有一个操作既有输入又有输出参数,输出参数必须在输入参数之后:
Slice不支持一个参数即是输入又是输出参数(通过参考调用)。 原因在于,对于远程调用,引用参数不会导致在编程语言中引用的相同的节省。 (数据仍然需要在两个方向复制,并且编组效率中的任何增益都可以忽略不计)。另外,参考(或输入 - 输出)参数会导致更复杂的语言映射,随之增加代码大小。
可选参数和返回值
从Ice 3.5开始,一个方操作的返回值和参数可以被声明为可选的,以指示程序可以使其值保持不变。 未声明为可选参数的参数称为必需参数; 一个程序必须提供所有必需参数的合法值。 在下面的讨论中,我们使用参数来引用输入参数,输出参数和返回值。
必须为每个可选参数分配唯一的非负整数标记:
标签的范围仅限于其操作,对其他操作没有影响。
操作的签名可以包括任何需要的和可选参数的组合,但是输出参数仍然必须在输入参数之后:
操作风格定义
如您所料,语言映射遵循Slice中使用的操作定义的样式:Slice返回类型映射到编程语言返回类型,而Slice参数映射到编程语言参数。
对于仅返回单个值的方法,通常从操作中返回值,而不是使用out-parameter。这种风格自然地映射成所有的编程语言。请注意,如果您使用外部参数,则在客户端上强制使用不同的API样式:大多数编程语言允许忽略函数的返回值,而通常无法忽略输出参数。
对于返回多个值的操作,通常将所有值作为out-parameters返回,并使用void的返回类型。但是,由于具有多个输出值的方法可以具有被认为比其余值更“重要”的一个特定值,因此规则并非全部清楚。一个常见的例子是一个迭代器操作,一个一个地从一个集合中返回项目:
next操作返回两个值:检索的记录和一个bool值,表示收集结束条件。 (如果返回值为false,则已经到达了集合的结尾,并且参数r具有未定义的值。)这种定义方式可能很有用,因为它自然符合程序员编写控制结构的方式。 例如:
操作重载
Slice不支持任何形式的操作重载。例如:
同一接口中的操作必须具有不同的名称,无论它们具有什么类型和数量的参数。 存在此限制,因为重载函数无法在没有内置支持的情况下映射到语言
幂等操作
一些操作,如Clock接口中的getTime,不会修改它们所操作对象的状态。setTime确实修改了对象的状态,但是也是幂等的。 您可以在Slice中注明如下:
这将getTime和setTime方法标记为幂等。如果操作的两个连续调用与单个调用具有相同的效果,则操作是等幂的。例如,x = 1;是一个幂等的操作,因为它是否执行一次或两次 - 无论是哪种方式,x都以值1结束。另一方面,x + = 1;不是一个幂等的操作,因为执行两次会产生一个与执行一次不同的x值。显然,只读操作是幂等的。
该幂等关键字是有用的,因为它允许Ice运行时间在执行自动重试以从错误中恢复时更加积极。具体来说,Ice保证操作调用最多一次语义:
对于正常(不是幂等)操作,Ice运行时间必须对如何处理错误保守。例如,如果客户端向服务器发送操作调用,然后失去连接性,则客户端运行时间无法确定其发送的请求是否实际将其发送到服务器。这意味着运行时间不能通过重新建立连接并再次发送请求来尝试从错误中恢复,因为这可能会导致第二次调用操作并违反最多一次语义;运行时间没有选择,而是将错误报告给应用程序。
对于幂等操作,另一方面,客户端运行时间可以尝试重新建立到服务器的连接,并且第二次安全地发送失败的请求。如果第二次尝试可以访问服务器,一切都很好,应用程序从不会注意到(暂时的)故障。只有第二次尝试失败才需要运行时报告错误回应用程序。 (可以使用Ice配置参数来增加重试次数。)
用户异常 User Exceptions
用户异常语法和语义
看看Clock接口中的setTime操作,我们发现一个潜在的问题:由于TimeOfDay结构使用short作为每个字段的类型,如果客户端调用setTime操作并传递一个具有无意义字段值的TimeOfDay值,会发生什么, 例如-199为分钟字段,或42为小时? 显然,向呼叫者提供一些没有意义的指示是很好的。 Slice允许您定义用户异常以指示客户端的错误状况。 例如:
用户异常非常像一个结构,它包含一些数据成员。 但是,与结构不同,异常可以具有零数据成员,也就是空。 像类一样,用户异常支持继承,可能包括可选的数据成员。
用户异常成员的默认值
你可以为具有以下类型的异常数据成员指定一个默认值:
An integral type (byte, short, int, long)
A floating point type (float or double)
string
bool
enum
例如:
在操作中定义用户异常
例外情况允许您在执行操作时出现错误情况,向客户端返回任意数量的错误信息。 操作使用异常规范来指示可能返回给客户端的异常:
此定义表示setTime操作可能会引发RangeError或Error用户异常(而不会导致其他类型的异常)。如果客户端收到RangeError异常,则该异常包含传递给setTime的TimeOfDay值,并导致错误(在errorTime成员中)以及可使用的最小和最大时间值(在minTime和maxTime成员中)。如果由于非法参数值引起的错误,setTime失败,则会引发错误。显然,因为Error没有数据成员,客户端将不知道是什么错误 - 它只是知道操作不起作用。
操作只能抛出其异常规范中列出的那些用户异常。如果在运行时,执行一个操作会抛出一个未列在其异常规范中的异常,则客户端会收到运行时异常),以指示该操作执行某些非法操作。为了表明操作不会引发任何用户异常,只需省略异常规范。 (Slice中没有空的异常规范。)
限制用户异常
异常不是一级的数据类型,一级的数据类型也不是异常:
你不能将异常作为参数值传递。
你不能使用异常作为数据成员的类型。
你不能将异常用作序列的元素类型。
你不能将异常用作字典的键或值类型。
你不能抛出非异常类型的值(例如int或string类型的值)。
这些限制的原因是某些实现语言对异常使用特定和单独的类型(与Slice相同)。 对于这样的语言,如果可以将其用作普通的数据类型,则很难映射异常。 (通过允许使用任意类型作为异常,C ++在编程语言中有些不寻常。)
用户异常继承
异常支持继承,例如:
这些定义设置了一个简单的异常层次结构:
ErrorBase位于树的根目录,并包含一个解释错误原因的字符串。
派生自ErrorBase的是RuntimeError和LogicError。 每个这些异常都包含一个枚举值,进一步对错误进行分类。
最后,RangeError来自LogicError,并报告具体错误的详细信息。
设置这样的异常层次结构不仅有助于创建更可读的规范,因为错误被分类,而且还可以在语言层面上使用,以获得良好的优势。 例如,Slice C ++映射保留异常层次结构,因此您可以将异常通常作为基础异常捕获,或者设置异常处理程序来处理特定的异常。
查看异常层次结构,不清楚在运行时,应用程序是否只会抛出大多数派生异常,例如RangeError,或者是否还会抛出基本异常,例如LogicError,RuntimeError和ErrorBase。 如果要表示基类异常,接口或类是抽象的(不会被实例化),则可以为该效果添加注释。
请注意,如果操作的异常指定表示特定的异常类型,则在运行时,该操作的实现也可能会导致更多的派生异常。 例如:
在这个例子中,op可能会抛出一个Base或Derived异常,也就是说,与异常规范中列出的异常类型兼容的任何异常都可以在运行时抛出。
随着系统的发展,将新的导出异常添加到现有的层次结构中是很常见的。 假设我们最初使用以下定义构建客户端和服务器:
还假设在现场部署了大量客户端,即升级系统时,无法轻松升级所有客户端。 随着应用程序的发展,系统将添加一个新的异常,服务器将重新部署到新的定义中:
这引发了如果服务器从doSomething中引发FatalApplicationError会发生什么的问题。 答案取决于客户端是使用旧的还是更新的定义构建的:
如果客户端使用与服务器相同的定义构建,那么它只会收到一个FatalApplicationError。
如果客户端是用原始定义构建的,该客户端就不知道FatalApplicationError是否存在。 在这种情况下,Ice运行时间会自动将异常切片到接收者理解的最为传统的类型(在这种情况下为Error),并丢弃特定于异常派生部分的信息。 (这完全类似于通过值捕获C ++异常 - 异常被切分为在catch子句中使用的类型。)
异常仅支持单继承。 (多重继承将难以映射成许多编程语言。)
运行时异常 Run-Time Exceptions
除了操作异常规范中列出的任何用户异常外,操作还可能会导致Ice运行时异常。 运行时异常是指定与平台相关的运行时错误的预定义异常。 例如,如果网络错误中断客户端和服务器之间的通信,则会通过运行时异常(例如ConnectTimeoutException或SocketException)通知客户端。
异常继承的结构
所有的Ice运行时和用户异常都安排在继承层次结构中,如下所示:
Ice运行时异常的完整层次结构:
许多运行时异常都有自解释的名称,如MemoryLimitException。 其他指示Ice运行时的问题,如EncapsulationException。 还有一些只能通过应用程序编程错误出现,例如TwowayOnlyException。 实际上,您可能永远不会看到这些例外情况。 但是,您将遇到一些运行时异常以及您应该知道的含义。
本地和远程异常 Local Versus Remote Exceptions
普通异常 Common Exceptions
在客户端检测到大多数错误条件。 例如,如果尝试联系服务器失败,则客户端运行时会引发ConnectTimeoutException。 但是,服务器检测到三个具体的错误条件(在Ice运行时异常层次结构图中显示为阴影),并通过Ice协议明确地显示给客户端运行时:ObjectNotExistException,FacetNotExistException和OperationNotExistException。
ObjectNotExistException
此异常指示请求已发送到服务器,但服务器无法找到具有嵌入在代理中的身份的servant。 换句话说,服务器找不到要发送请求的对象。
ObjectNotExistException是一个死亡证书:它表示服务器中的目标对象不存在。
Ice run time只有在没有匹配的身份的方面存在时才会引发ObjectNotExistException; 否则,它会引发FacetNotExistException。
最可能的情况是这种情况,因为该对象在过去存在一段时间,并且已被破坏,但是如果客户端使用具有从未创建的对象的身份的代理,那么也会引发相同的异常。 如果您收到此异常,则需要清理可能已分配的与您收到此异常的特定对象相关的资源。
FacetNotExistException
客户端尝试联系对象的不存在的facets,即,服务器至少有一个具有给定身份的servant,但没有具有匹配facet名称的仆人。
OperationNotExistException
如果服务器能够找到具有正确标识的对象,但在试图发送客户机的操作调用时,服务器发现目标对象没有这样的操作,则会触发此异常。
你只会在两种情况下看到此异常:
1你对不正确类型的代理使用了未经检查的down-cast。
2客户端使用指定操作存在的对象的接口定义构建客户端,但服务器是使用不同版本的接口定义,其中不存在这个操作
未知异常 Unknown Exceptions
服务器端上的任何错误条件,在前面三个异常中没有描述的情况,都被客户称为三个泛型异常之一(在Ice运行时异常层次图中显示的是阴影):UnknownUserException,UnknownLocalException, UnknownException.
UnknownUserException
这个异常指示操作实现抛出了一个Slice异常,该异常在操作的异常规范中没有声明(并且不是从操作的异常规范中的一个异常派生出来的)。
UnknownLocalException
如果一个操作的实现提出了一个运行时异常除了ObjectNotExistException,FacetNotExistException,或OperationNotExistException(如NotRegisteredException),客户端收到一个UnknownLocalException。换句话说,Ice协议不传输服务器中遇到的确切异常,但是在答复中简单地向客户机返回一点,表明服务器遇到运行时异常。
UnknownException
一个操作已经抛出一个非Ice异常。例如,如果服务器中的操作抛出一个c++异常,例如char *或Java异常,例如ClassCastException,客户端将接收到UnknownException。