Ice中间件学习 02 【容易实现HelloWorld】
注:该部分内容部分来自网络,如有侵权请联系
一、简述
创建一个非常简单的客户-服务器Ice应用,这个应用提供远程地打印功能:客户端发送要打印的文本给服务器,再由服务器把文本发给打印机,打印程序只是把文本打印到终端,而不是真正的打印机,目的是说明客户怎样与服务器通信。
二、安装Ice
Ice官方下载:http://www.zeroc.com/download.html
2.1、安装Ice(过程略)
2.2、配置路径
配置环境变量:将Ice的安装路径下的bin目录配置到系统的path环境变量中。如下图:
图-1
测试安装是否成功,只需要在安装的机器上打开cmd输入slice2java命令即可(该系列所有文章针对Java语言实现),如下图所示:
图-2
说明Ice配置完成!
三、HelloWorld实现
3.1、使用MyEclipse开发,创建一个项目为iceTest的Java工程,在src
3.2、编写Slice定义
编写任何Ice应用的第一个步骤就是要编写一个Slice定义,其中包含应用所用的各个接口,我们为打印应用编写了如下这样的Slice定义:
module demo{ interface Printer{ void printString(string s); }; };
我们把这段代码片段保存在叫做printer.ice的文件中,存放在src目录下,如下图所示
图-3
Slice定义含有一个接口,叫做Printer,该接口非常简单,只提供了一个操作叫做printString,printString操作接收一个串作为它唯一的输入参数;这个串的文本将会出现在打印机上。
3.3、编写使用Java的Ice应用
3.3.1、编译printer.ice
在cmd命令中进入iceTest工程中printer.ice文件所在的目录下,如下图所示:
图-4
cmd中输入命令:slice2java printer.ice
图-5
说明:slice2java编译器根据这个printer.ice文件定义生成一些Java源文件,我们目前无需关注这些文件的确切内容,它们所包含的是编译器生成的代码,与我们在printer.ice中定义的Printer接口相对应。
此时刷新iceTest工程,可以看到多出了很多的文件,之所以编译以后生成的Java文件存放在demo下,是因为我们的Printer.ice文件中使用module demo定义了包路径。
图-6
说明:demo包下的所有Java文件都报错,这是因为没有导入Ice运行所需要的jar包,我们在Ice的安装目录下的lib包找到ice.jar,将该jar包导入到iceTest工程下即可。
图-7
3.3.2、编写和编译服务器
要实现Printer接口,必须创建一个servant类,按照惯例,servant类的名字是接口的名字加上一个I后缀,所以servant类叫做PrintI,并放在PrinterI.java源文件中,存放在com.xuz.servant包中。
package com.xuz.servant; import Ice.Current; import demo._PrinterDisp; public class PrintI extends _PrinterDisp { @Override public void printString(String s, Current current) { System.out.println(s); } }
说明:PrintI类继承自叫做_PrinterDisp的基类,该类由slice2java编译器生成,是一个抽象类,其中含有一个printString方法,其参数时打印机要打印的串,以及类型为Ice.Current的对象,我们的printString方法的实现会简单地把它的参数写到终端,服务器代码其余部分在一个叫做Server.java的文件中,存在com.xuz.server包中,下面给出了其完整的代码:
package com.xuz.server; import com.xuz.servant.PrintI; public class Server { public static void main(String[] args) { int status = 0; Ice.Communicator ic = null; try { //初始化连接,args可以传一些初始化参数,如连接超时,初始化客户端连接池的数量等 ic = Ice.Util.initialize(args); //创建名为SimplePrinterAdapter的适配器,并要求适配器使用缺省的协议(TCP/IP 端口为10000的请求) Ice.ObjectAdapter adapter = ic.createObjectAdapterWithEndpoints("SimplePrinterAdapter", "default -p 10000"); //实例化一个Printer对象,为Printer接口创建一个服务对象 Ice.Object object = new PrintI(); //将服务单元增加到适配器中,并给服务对象指定名称为SimplePrinter,该名称用于唯一确定一个服务单元 adapter.add(object, Ice.Util.stringToIdentity("SimplePrinter")); //激活适配器 adapter.activate(); //让服务在退出之前,一直持续对请求的监听 ic.waitForShutdown(); } catch (Exception e) { e.printStackTrace(); status = 1; }finally{ if(ic!=null){ ic.destroy(); } } System.exit(status); } }
说明:main的主题含有一个try块,把所有的服务器代码都放在其中;这段代码会在退出之前销毁通信器(如果曾今成功创建过),要是Ice run time正常结束,这样做是必须的:程序必须调用它所创建的任何通信器的destroy;否则就会产生不确定的行为。我们把对destory的调用放进finally块,这样不管前面的try块中发生什么异常,通信器都保证会正确销毁。
try块中主体含有实际的服务器代码:
//初始化连接,args可以传一些初始化参数,如连接超时,初始化客户端连接池的数量等 ic = Ice.Util.initialize(args); //创建名为SimplePrinterAdapter的适配器,并要求适配器使用缺省的协议(TCP/IP 端口为10000的请求) Ice.ObjectAdapter adapter = ic.createObjectAdapterWithEndpoints("SimplePrinterAdapter", "default -p 10000"); //实例化一个Printer对象,为Printer接口创建一个服务对象 Ice.Object object = new PrintI(); //将服务单元增加到适配器中,并给服务对象指定名称为SimplePrinter,该名称用于唯一确定一个服务单元 adapter.add(object, Ice.Util.stringToIdentity("SimplePrinter")); //激活适配器 adapter.activate(); //让服务在退出之前,一直持续对请求的监听 ic.waitForShutdown();
这段代码包含了一下步骤:
1)、我们调用Ice.Util.initialize初始化Ice run time(之所以把args传给这个调用,是因为服务器可能有run time 感兴趣的命令行参数;就这个例子而言,服务器不需要任何命令参数)。对initialize的调用返回的是一个通信器Ice.Communicator引用,这个引用是Ice run time的主句柄。
2)、调用通信Communicator实例上的createObjectAdapterWithEndpoints,创建一个对象适配器,传入的参数是:SimplePrinterAdapter(适配器的名字)。
3)、这时服务器run time已经初始化,实例化一个PrintI对象,为我们的Printer接口创建一个servant。
4)、调用适配器add,告诉它有了一个新的servant;传给add的参数时我们刚才实例化的servant,再加上一个标示符,在这里"SimplePrinter"串是servant的名字(如果有多个打印机,每个打印机都有不同的名字,更正确的说法是,都会有不同的标示符)。
5)、接下来,调用适配器的activate方法激活适配器(适配器一开始是在holding状态创建,如果适配器处于holding状态,服务器端run time就会停止从对应的传输端点读取数据,不接受客户端发送的连接请求,这种做法在下面这样的情况下很有用:如果有很多歌servant,它们共享同一个适配器,而在所有servant实例化之前我们不想处理请求)。一旦适配器被激活,服务器就会开始处理来自客户端的请求。
6)、最后调用waitForShutdown。这个方法挂起发出调用的线程,直到服务器实现终止为止-或者是通过发出的一个调用关闭run time,或者是对某个信号做出相应(目前,当我们不再需要服务器时,会简单地在命令行上终端它)。
注意:尽管这里的代码不算少,但它们对所有的服务器都是一样的,你可以把这些代码放在一个辅助类里,然后就无需再为它费心了(Ice提供了一个辅助类:Ice.Application)。就实际的应用代码而言,服务器只有几行代码:六行代码定义PrintI类,再加上三、四行代码实例化一个PrintI对象,并向对象适配器注册它。
3.3.3、编写和编译客户端
客户端代码在Client.java中,看起来与服务器非常类似,存放在com.xuz.client包中,代码如下:
package com.xuz.client; import demo.PrinterPrx; import demo.PrinterPrxHelper; public class Client { public static void main(String[] args) { int status = 0; Ice.Communicator ic = null; try { //初始化通信器 ic = Ice.Util.initialize(args); //传入远程服务单元的名称、网络协议、IP以及端口,获取Printer的远程代理,这里使用stringToProxy方式 Ice.ObjectPrx base = ic.stringToProxy("SimplePrinter:default -p 10000"); //通过checkedCast向下转型,获取Printer接口的远程,并同时检测根据传入的名称获取服务单元是否Printer的代理接口,如果不是则返回null对象 PrinterPrx printer = PrinterPrxHelper.checkedCast(base); if(printer == null){ throw new Error("Invalid proxy"); } //把Hello world传给服务端,让服务端打印出来,因为这个方法最终会在服务端上执行 printer.printString("Hello World!"); } catch (Exception e) { e.printStackTrace(); status = 1; }finally{ if(ic != null){ ic.destroy(); } } System.exit(status); } }
说明:和服务器一样
1)、调用Ice.initialize初始化Ice run time.
2)、获取远地打印机的代理,调用通信器的stringToProxy创建一个代理,所用参数是:"SimplePrinter:default -p 10000",注意,这个串包含了对象标示符和服务器所用的端口号一致
3)、stringToProxy返回的代理类型是Ice.ObjectPrx,这种类型位于接口和类的继承树的根部,但是实际与我们的打印机交谈,我们需要的嘶吼Printer接口、而不是Object接口的代理,为此,需要调用PrinterPrxHelper.checkCast进行向下转型,这个方法会发送一条消息给服务器,实际询问"这是Printer接口的代理吗?",如果是,这个调用就会返回Printer的一个代理,如果代理代表的是其他类型的接口,这个调用就会返回一个空代理。
4)、测试向下转型是否成功,如果不成功,抛出错误消息,终止客户。
5)、现在,在地址空间里面有了一个活的代理,可以调用printerString方法,把hello world串传给它,服务器会在它的终端上打印出这个串。
3.3.4、运行服务和客户端
在MyEclipse中,首先运行Server,启动服务器,再运行Client,在服务器的Console中我 们会看到打印出了"Hello World!".
四、总结
在本节主要介绍了一个简单的入门级(完整)的客户端和服务器的实例,从上可知,编写Ice应用涉及一下几个步骤:
1、编写Slice定义并编译它。
2、编写服务器并编译它。
3、编写客户端并编译它。
五、源码下载:
Ice简单应用源码下载