ICE与分布式编程
网络通信引擎(Ice)是一个面向对象的工具包,使您能够以最小的工作量构建分布式应用程序。 Ice允许您将精力集中在应用程序逻辑上,它负责与低级网络编程接口的所有交互。有了Ice,就不必担心打开网络连接、序列化和反序列化数据以进行网络传输或重试失败的连接尝试等细节。
Ice的主要设计目标是:
l 提供适合在异构环境中使用的面向对象的中间件平台。
l 提供一整套功能,支持为各种领域开发现实的分布式应用程序。
l 避免不必要的复杂性,使平台易于学习和使用。
l 提供在网络带宽,内存使用和CPU开销方面有效的实施。
l 提供具有内置安全性的实现,使其适合在不安全的公共网络上使用。
简单来说,Ice设计目标可以表述为“让我们构建一个强大的中间件平台,使开发人员的开发工作因此更轻松”。
获得ICE的帮助
如果您有问题,但在本手册中找不到答案,则可以访问我们的开发人员论坛,了解其他开发人员是否遇到过相同的问题。如果您仍然需要帮助,请随时在论坛上发布您的问题,这是ZeroC的开发人员定期监控的。但请注意,我们只能在我们的论坛提供有限的免费支持。为了保证响应和解决问题的时间,我们强烈建议购买商业支持。
关于手册的反馈
如果您在本手册中发现任何错误(无论如何轻微),我们非常愿意听到您的意见。我们也想听听您对内容的意见,以及任何有关如何改进的建议。您可以通过电子邮件icebook@zeroc.com与我们联系。
法律声明
制造商和销售商用于区分其产品名称的被称为商标。如果这些名称出现在本书中,并且ZeroC意识到商标声明,则这些名称以初始大写或全部大写印刷。作者和出版商在编写本书时已小心谨慎,但不作任何明示或暗示的保证,对错误或遗漏不承担任何责任。对于因使用本文所包含的信息或程序而导致的或与之相关的偶然或必然损害,我们不承担任何责任。
许可
本手册根据两种许可证之一提供,以您喜欢的为准:
知识共享署名 - 无衍生作品3.0未上传的许可证。
此许可证不允许您进行修改。
知识共享署名 - 非商业性使用 - 相同方式共享3.0 Unported许可。
此许可证允许您进行修改,但仅限非商业用途。如果您在本许可证下分发本手册,则必须明确包括以下文本:
本文档源自ZeroC的Ice手册,版权所有©ZeroC,Inc. 2003-2015。
您可以在以下位置找到此文档的最新版本:https://doc.zeroc.com/display/Ice/Ice+Manual
版权
Copyright ©2003-2016 by ZeroC, Inc.
mailto:info@zeroc.com
https://zeroc.com
Hello world程序
编写一个slice接口定义
编写任何Ice应用程序的第一步是编写一个包含应用程序使用的接口的Slice定义。对于我们最小的打印应用程序,我们编写以下Slice定义:
module Demo {
interface Printer {
void printString(string s);
};
};
我们将此文本保存在名为Printer.ice的文件中。
我们的Slice定义包含模块Demo,其中包含一个名为Printer的单一接口。现在,界面非常简单,只提供一个操作,称为printString。 printString操作接受一个字符串作为其唯一的输入参数; 该字符串的文本是(可能是远程)打印机上显示的内容。
用Java编写一个Ice应用程序
编译Java的slice定义
创建ICE Java应用程序的第一步是编译我们的Slice定义来生成Java代理和骨架。您可以编译定义如下:
$ mkdir generated
$slice2java --output-dir generated Printer.ice
--output-dir选项指示编译器将生成的文件放入生成的目录。 这避免了使用生成的文件使工作目录变得混乱。 slice2java编译器从此定义生成多个Java源文件。这些文件的确切内容现在不涉及我们 - 它们包含对应于我们在Printer.ice中定义的打印机接口的生成代码。
在Java中编写和编译服务器
要实现我们的打印机接口,我们必须创建一个servant类。按照惯例,servant类使用其接口的名称带有I后缀,因此我们的servant类称为PrinterI,并放入源文件PrinterI.java中:
public class PrinterI extends Demo._PrinterDisp {
public void
printString(String s, Ice.Current current)
{
System.out.println(s);
}
}
PrinterI类继承自一个名为_PrinterDisp的基类,它由slice2java编译器生成。 基类是抽象的,包含一个printString方法,接受打印机打印的字符串和Ice.Current类型的参数。(现在我们将忽略Ice.Current参数。)我们的printString方法的实现只是将它的参数写入终端。
服务器代码的其余部分位于名为Server.java的源文件中,完整显示在此处:
public class Server {
public static void
main(String[] args)
{
int status = 0;
Ice.Communicator ic = null;
try {
ic = Ice.Util.initialize(args);
Ice.ObjectAdapter adapter=
ic.createObjectAdapterWithEndpoints("SimplePrinterAdapter", "default -p 10000");
Ice.Object object = new PrinterI();
adapter.add(object,ic.stringToIdentity("SimplePrinter"));
adapter.activate();
ic.waitForShutdown();
} catch (Ice.LocalException e) {
e.printStackTrace();
status = 1;
} catch (Exception e) {
System.err.println(e.getMessage());
status = 1;
}
if (ic != null) {
// Clean up
//
try {
ic.destroy();
} catch (Exception e) {
System.err.println(e.getMessage());
status = 1;
}
}
System.exit(status);
}
}
注意代码的一般结构:
public class Server {
public static void
main(String[] args)
{
int status = 0;
Ice.Communicator ic = null;
try {
//服务实现代码
} catch (Ice.LocalException e) {
e.printStackTrace();
status = 1;
} catch (Exception e) {
System.err.println(e.getMessage());
status = 1;
}
if (ic != null) {
// Clean up
//
try {
ic.destroy();
} catch (Exception e) {
System.err.println(e.getMessage());
status = 1;
}
}
System.exit(status);
}
}
main的主体包含一个try块,我们在其中放置所有的服务器代码,然后是两个catch块。第一个块捕获Ice运行时可能抛出的所有异常;其意图是,如果代码在任何地方遇到意外的Ice运行时异常,堆栈将一直展开回main,这将打印异常,然后向操作系统返回失败。第二个块捕获异常异常;意图是如果我们在代码中遇到致命错误条件,我们可以简单地抛出一个错误消息的异常。同样,这会将堆栈退回到main,这将打印错误消息,然后将故障返回到操作系统。
在代码退出之前,它会销毁通信器(如果已成功创建)。这是必要的,以正确完成Ice运行时间:程序必须调用destroy它创建的任何通信器;否则,未定义的行为结果。
我们的try块的正文包含实际的服务器代码:
ic = Ice.Util.initialize(args);
Ice.ObjectAdapter adapter=
ic.createObjectAdapterWithEndpoints("SimplePrinterAdapter", "default -p 10000");
Ice.Object object = new PrinterI();
adapter.add(object,ic.stringToIdentity("SimplePrinter"));
adapter.activate();
ic.waitForShutdown();
代码执行以下步骤:
1、 我们通过调用Ice.Util.initialize初始化Ice运行参数。 (我们将参数传递给此调用,因为服务器可能具有运行时感兴趣的命令行参数;对于此示例,服务器不需要任何命令行参数)。对initialize的调用返回Ice.Communicator引用,这是Ice运行时的主要对象。
2、 我们通过在Communicator实例上调用createObjectAdapterWithEndpoints创建一个对象适配器。我们传递的参数是“SimplePrinterAdapter”(它是适配器的名称)和“default -p 10000”,它指示适配器使用端口号10000处的默认协议(TCP / IP)监听传入请求。
3、 此时,服务器端运行时被初始化,我们通过实例化一个PrinterI对象来为我们的Printer接口创建一个servant。
4、 我们通过调用add在适配器上通知对象适配器存在一个新的servant;要添加的参数是我们刚刚实例化的servant,加上一个标识符。在这种情况下,字符串“SimplePrinter”是Ice对象的名称。(如果我们有多个打印机,每个打印机都会有不同的名称,或更准确地说,不同的对象标识。)
5、 接下来,我们通过调用它的activate方法激活适配器。 (适配器最初以保持状态创建;如果我们有许多服务器共享同一适配器,并且不希望处理请求直到所有服务器都被实例化之后,这是有用的)。
6、 最后,我们调用waitForShutdown。此调用暂停调用线程,直到服务器实现终止,通过调用关闭运行时间,或响应信号。(现在,当我们不再需要它时,我们将简单地在命令行中断服务器。)
注意,即使在这里有相当多的代码,该代码对于所有服务器本质上是相同的。你可以把这个代码放入一个帮助类,将其封装起来。(Ice提供了一个这样的帮助类,称为Ice.Application。)就实际应用程序代码而言,服务器只包含几行:PrinterI类的定义有七行,另外三行用于实例化PrinterI对象,将其注册到对象适配器。
我们可以编译服务器代码如下:
$ mkdir classes
$ javac -d classes -classpath classes:$ICE_HOME/lib/Ice.jar \
Server.java PrinterI.java generated/Demo/*.java
这将编译我们的应用程序代码和Slice编译器生成的代码。我们假设ICE_HOME环境变量设置为包含Ice运行时间的顶级目录。(例如,如果您在/ opt / Ice中安装了Ice,请将ICE_HOME设置为该路径。)请注意,Ice for Java使用ant构建环境来控制源代码的构建。(ant类似于make,但对于Java应用程序更灵活。)你可以看看Ice附带的演示代码,看看如何使用这个工具。
在Java中编写和编译客户端
Client.java中的客户端代码看起来非常类似于服务器。 代码如下:
public class Client {
public static void
main(String[] args)
{
int status = 0;
Ice.Communicator ic = null;
try {
ic = Ice.Util.initialize(args);
Ice.ObjectPrx base = ic.stringToProxy("SimplePrinter:default -p 10000");
Demo.PrinterPrx printer= Demo.PrinterPrxHelper.checkedCast(base);
if (printer == null)
throw new Error("Invalid proxy");
printer.printString("Hello World!");
} catch (Ice.LocalException e) {
e.printStackTrace();
status = 1;
} catch (Exception e) {
System.err.println(e.getMessage());
status = 1;
}
if (ic != null) {
// Clean up
//
try {
ic.destroy();
} catch (Exception e) {
System.err.println(e.getMessage());
status = 1;
}
}
System.exit(status);
}
}
注意,总体代码布局与服务器相同:我们使用相同的try和catch块来处理错误。 try块中的代码执行以下操作:
1、 至于服务器,我们通过调用Ice.Util.initialize初始化Ice运行环境。
2、 下一步是获取远程打印机的代理。我们通过在通信器上调用stringToProxy创建一个代理,使用字符串“SimplePrinter:default -p 10000”。请注意,该字符串包含服务器使用的对象标识和端口号。(显然,将对象标识和端口号硬编码到我们的应用程序是一个坏主意,但它现在将做;当我们讨论IceGrid时,我们将看到更多的建筑方法这样做)
3、 stringToProxy返回的代理类型为Ice.ObjectPrx,它位于接口和类的继承树的根。但是实际上我们打印机,我们需要一个代理打印机接口,而不是一个对象接口。为此,我们需要通过调用PrinterPrxHelper.checkedCast来执行下载。选中的转储向服务器发送消息,有效地询问“这是打印机接口的代理吗?如果是,调用返回一个类型为Demo :: Printer的代理;否则,如果代理表示某个其他类型的接口,则调用返回null。
4、 我们测试downcast成功,如果不是,抛出一个错误消息终止客户端。
5、 现在在我们的地址空间中有一个活动代理,可以调用printString方法,传递历史悠久的“Hello World!”字符串。服务器在其终端上打印该字符串。
编译客户端看起来与服务器大致相同:
$ javac -d classes -classpath classes:$ICE_HOME/lib/Ice.jar\
Client.java PrinterI.java generated/Demo/*.java
在Java中运行客户端和服务器
要运行客户端和服务器,我们首先在单独的窗口中启动服务器:
$ java Server
在这一点上,我们不会看到任何东西,因为服务器只是等待客户端连接到它。我们在不同的窗口中运行客户端:
$ java Client
$
客户端运行并退出而不生成任何输出; 但是,在服务器窗口中,我们看到“Hello World!其由打印机产生。为了摆脱服务器,我们现在在命令行中断它。(我们将在我们的Ice.Application的讨论中看到更清晰的终止服务器的方法。)
如果出现问题,客户端将打印一条错误消息。例如,如果我们在没有首次启动服务器的情况下运行客户端,则会得到如下所示的结果:
Ice.ConnectionRefusedException
error= 0
atIceInternal.ConnectRequestHandler.getConnection(ConnectRequestHandler.java:240)
atIceInternal.ConnectRequestHandler.sendRequest(ConnectRequestHandler.java:138)
atIceInternal.Outgoing.invoke(Outgoing.java:66)
atIce._ObjectDelM.ice_isA(_ObjectDelM.java:30)
atIce.ObjectPrxHelperBase.ice_isA(ObjectPrxHelperBase.java:111)
atIce.ObjectPrxHelperBase.ice_isA(ObjectPrxHelperBase.java:77)
atDemo.HelloPrxHelper.checkedCast(HelloPrxHelper.java:228)
atClient.run(Client.java:65)
Causedby: java.net.ConnectException: Connection refused
...
注意,要成功运行客户端和服务器,CLASSPATH必须包括Ice库和classes目录,例如:
$ export CLASSPATH=$CLASSPATH:./classes:$ICE_HOME/lib/Ice.jar