您的位置:首页 > 其它

ICEdemo

2016-06-18 11:18 756 查看
http://ronbay.iteye.com/blog/1697217

http://blog.chinaunix.net/uid-126438-id-2924657.html

 

 

1、ICE是什么? 

ICE是ZEROC的开源通信协议产品,它的全称是:The Internet Communications Engine,翻译为中文是互联网通信引擎,是一个面向对象的中间件,使我们能够以最小的代价构建分布式应用程序。ICE使我们专注于应用逻辑的开发,它来处理所有底层的网络接口编程,这样我们就不用去考虑这样的细节:打开网络连接、网络数据传输的序列化与反序列化、连接失败的尝试次数等。 

2、为什么会有ICE? 

ICE是分布式应用的一种比较好的解决方案,虽然现在也有一些比较流行的分布式应用解决方案,如微软的.NET(以及原来的DCOM)、CORBA及WEB SERVICE等,但是这些面向对象的中间件都存在一些不足: 

.NET是微软产品,只面向WINDOWS系统,而实际的情况是在当前的网络环境下,不同的计算机会运行不同的系统,如LINUX上面就不可能使用.NET; 

CORBA虽然在统一标准方面做了很多的工作,但是不同的供应商实现之间还是缺乏互操作性,并且目前还没有一家供应商可以针对所有的异种环境提供所有的实现支持,且CORBA的实现比较复杂,学习及实施的成本都会比较高; 

WEB SERVICE最要命的缺点就是他的性能问题,对于要求比较高的行业是很少会考虑WEB SERVICE的。 

ICE的产生就是源于.NET、CORBA及WEB SERVICE这些中间件的不足,它可以支持不同的系统,如WINDOWS、LINUX等,也可以支持在多种开发语言上使用,如C++、C、JAVA、RUBY、PYTHON、VB等,服务端可以是上面提到的任何一种语言实现的,客户端也可以根据自己的实际情况选择不同的语言实现,如服务端采用C语言实现,而客户端采用JAVA语言实现,底层的通讯逻辑通过ICE的封装实现,我们只需要关注业务逻辑。 

3、ICE是如何工作的? 

Ice 是一种面向对象的中间件平台,这意味着 Ice为构建面向对象的客户-服务器应用提供了工具、API 和库支持。要与Ice持有的对象进行通信,客户端必须持有这个对象的代理(与CORBA的引用是相同的意思),这里的代理指的是这个对象的实例,ICE在运行时会定位到这个对象,然后寻找或激活它,再把In参数传给远程对象,再通过Out参数获取返回结果。 

这里提到的代理又分为直接代理和间接代理,直接代理其内部保存有某个对象的标识,以及它的服务器的运行地址;间接代理指的是其内部保存有某个对象的标识,以及对象适配器名(object adapter name),间接代理没有包含寻址信息,为了正确地定位服务器,客户端在运行时会使用代理内部的对象适配器名,将其传给某个定位器服务,比如IcePack服务,然后,定位器会把适配器名当作关键字,在含有服务器地址的表中进行查找,把当前的服务器地址返回给客户,客户端 run time现在知道了怎样联系服务器,就会像平常一样分派 (dispatch)客户请求。 

ICE可以保证在任何的网络环境或者操作系统下,成功的调用只有一次,它在运行时会尽力的定位到远程服务器,在连接失败的情况下会做尝试性重复性连接,确实连不上的情况会给用户以提示。 

客户端在调用服务端的方法时,可以采取同步或异步的方式实现,同步调用就相当于调用自己本地的方法一样,其它行为会被阻塞;异步调用是非常有用的调用方式,如服务端需要准备的数据来自于其它异步接口,这个时候客户端就不需要等待,待服务端数据准备充份后,以消息的方式通知客户端,服务端就可以去干其它的事情了,而客户端也可以到服务端获取数据了。 

4、ICE调用模式 

ICE采用的网络协议有TCP、UDP以及SSL三 种,不同于WebService,ICE在调用模式上有好几种选择方案,并且每种方案正对不同的网络协议的特性做了相应的选择。 

Oneway(单向调用):客户端只需将调用注册到本地传输缓冲区(Local Transport Buffers)后就立即返回,不会等待调用结果的返回,不对调用结果负责。 

Twoway(双向调用):最通用的模式,同步方法调用模式,只能用TCP或SSL协议。 

Datagram(数据报):类似于Oneway调用,不同的是 Datagram调用只能采用UDP协议而且只能调用无返回值和无输出参数的方法。 

BatchOneway(批量单向调用):先将调用存 在调用缓冲区里面,到达一定限额后自动批量发送所有请求(也可手动刷除缓冲区)。 

BatchDatagram(批量数据报):与上类似。 

不同的调用模式其实对应着不动的业务,对于大部分的有返回值的或需要实时响应的方法,我们可能都采用Twoway方式调用,对于一些无需返回值或 者不依赖返回值的业务,我们可以用Oneway或者BatchOneway方式,例如消息通知;剩下的Datagram和BatchDatagram方式 一般用在无返回值且不做可靠性检查的业务上,例如日志。 

 

 

配置ICE开发环境

首先,从http://www.zeroc.com/download.html 下载ICE,目前最新版本是Ice-3.3.1。下载页面里除了ICE的源码之外,也提供了VC或C++Builder的已编译安装包以及各Linux版本的RPM下载。

如果下载的是源码版本,编译方法是(以VC2005 Express为例):

1. ICE需要一些第三方库,在编译ICE之前要先编译第三方库,清单如下(它们也能在ICE官网上下载):

Berkeley DB
expat
OpenSSL
bzip2
mcpp


2. 编译完上面这些库以后,把它们放到同一个目录中,然后设置环境变量THIRDPARTY_HOME:

set THIRDPARTY_HOME = d:\ice3party


3. 打开$ICE/cpp/Make.rules.mak,找到CPP_COMPILER变量,改成CPP_COMPILER = VC80_EXPRESS

    这个依据你的编译器决定,可以是VC60, VC80, VC80_EXPRESS, VC90,VC90_EXPRESS, BCC2007, BCC2009

4. 如果想让编译的库能脱离VC运行时库,打开$ICE/cpp/Make.rules.msvc,把CPPFLAGS    =$(CPPFLAGS) -MD改成CPPFLAGS   
=$(CPPFLAGS) -MT。

5. 在命令行下进入$ICE/cpp目录,输入nmake -f Makefile.mak开始编译。默认是编译成Debug模式的DLL库。如果想编译成静态库,可以设置变量STATICLIBS=yes;想编译成Release模式,设置OPTIMIZE=yes。如

nmake -f Makefile.mak STATICLIBS=yes OPTIMIZE=yes


如果按上面方法设置,应该不会有问题。

6. 最最后,把bin目录加入path变量,以便让系统能找到ICE的dll文件(其实主要是三个dll文件,ice33.dll、iceutil33.dll和bzip2.dll)

以后编译ICE的程序时,把上面编译好或直接下载的已编译版本的ice.lib和iceutil.lib(或Debug版本的iced.lib和iceutild.lib)链接入项目即可。

 

ICE的HelloWorld

跨语言的分布式系统首先要定义一个与编程语言无关的接口描述语法,用于分布于各处的服务器与客户端之间对话。比如DCOM和CORBA使用IDL语法,SOAP使用WSDL语法,当然还有时下流行的JSON。

ICE使用的是称为Slice(SpecificatoinLanguage for Ice)的语法,Slice语法和C++(或Java,C#)比较相近,只要会C++(或Java,C#)很容易就能写Slice定义了

下面是一个简单的接口的Slice定义:

1. module Demo {
2.     interface Printer {
3.         void printString(string s);
4.     };
5. };

它定义一个Printer接口(interface),这个接口只有一个printString方法,输入参数是一个字符串(string)。最后,这个接口位于Demo模块(module)之下。

把它保存为Printer.ice后接着我们使用slice2cpp程序依据这个Slice定义生成C++使用的头文件和对应的代理代码:

slice2cpp Printer.ice


如果没提示错误,就会生成Printer.h和Printer.cpp,把这两个文件加入到服务器端项目和客户端项目后就可以互相对话了。

下表是Slice与C++的映射关系

Slice
C++
#include

#include

#ifndef

#ifndef

#define

#define

#endif

#endif

module

namespace

bool

bool

byte

Ice::Byte

short

Ice::Short

int

Ice::Int

long

Ice::Long

float

Ice::Float

double

Ice::Double

string

Ice::string

enum

enum(不支持指定数字)

struct

struct

class

class(所有方法都是纯虚函数)

interface

struct(所有方法都是纯虚函数,没有成员变量)

sequence

std::vector

dictionary

std::map

exception Err

class Err:public Ice:UserException

nonmutating方法限定符

const方法

idempotent方法限定符

-

out 参数限定符

引用类型

*

对应类型的代理类

参考这个表,可以知道上面的Slice定义对应的C++映射如下:

1. namespace Demo {
2.     struct Printer {
3.         virtual void printString(string s) = 0;
4.     };
5. };

(严格地说,C++中对应的Printer类还继承自IceProxy::Ice::Object,目前我们可以不理这个问题)

我们只要在服务器端实现这个printString方法,就可以在客户端简单地调用它了。

编写服务器端代码:

1.       新建一个控制台项目
2.       将$ICE\include添加到头文件目录列表中
3.       将$ICE\lib\ice.lib和iceutil.lib(对应的Debug版本是iced.lib和iceutild.lib)链接入项目
4.       把生成Printer.cpp加入项目

1. #include 
2. #include "printer.h"  //slice2cpp生成的文件
3.  
4. using namespace std;
5. using namespace Demo;
6.  
7. //实现printString方法
8. struct PrinterImp : Printer{
9.     virtual void printString(const ::std::string& s,
10.         const ::Ice::Current& = ::Ice::Current())
11.     {
12.         cout << s << endl;   
13.     }
14. };
15.  
16. int main(int argc, char* argv[])
17. {
18.     Ice::CommunicatorPtr ic;
19.  
20.     try{
21.         // 初始化Ice运行库
22.         ic = Ice::initialize(argc, argv);
23.         // 建立ObjectAdapter,命名为SimplePrinterAdapter
24.         // 使用默认协议(一般是tcp)并在10000端口监听。
25.         Ice::ObjectAdapterPtr adapter
26.             = ic->createObjectAdapterWithEndpoints(
27.                 "SimplePrinterAdapter", "default -p 10000");
28.         // 把我们实现的Printer加入ObjectAdapter,并命名为SimplePrinter
29.         Ice::ObjectPtr object = new PrinterImp;
30.         adapter->add(object, ic->stringToIdentity("SimplePrinter"));
31.         adapter->activate();
32.         // 等待直到Communicator关闭
33.         ic->waitForShutdown();
34.     }
35.     catch(const Ice::Exception &e){
36.         cerr << e << endl;
37.     }
38.     catch(const char* msg){
39.         cerr << msg << endl;
40.     }
41.     // 回收Ice运行库所用的资源
42.     if(ic) ic->destroy();
43.    
44.     return 0;
45. }

客户端代码:

1. #include 
2. #include 
3.  
4. using namespace std;
5. using namespace Demo;
6.  
7. int main(int argc, char* argv[])
8. {
9.     Ice::CommunicatorPtr ic;
10.     try{
11.         // 初始化Ice运行库
12.         ic = Ice::initialize(argc, argv);
13.         // 在10000端口取得SimplePrinter代理对象
14.         Ice::ObjectPrx base = ic->stringToProxy("SimplePrinter:default -p 10000");
15.         // 把对象转换成Printer代理
16.         PrinterPrx printer =  PrinterPrx::checkedCast(base);
17.         if(!printer) throw "Invalid Proxy!";
18.         // 能过这个代码调用printString方法
19.         printer->printString("Hello World!");
20.     }
21.     catch(const Ice::Exception &e){
22.         cerr << e << endl;
23.     }
24.     catch(const char* msg){
25.         cerr << msg << endl;
26.     }
27.     // 回收Ice运行库所用的资源
28.     if(ic) ic->destroy();
29.    
30.     return 0;
31. }

编译服务器端和客户端,然后启动一个服务器端,每次调用客户端后服务器端会显示一行Hello world!

你也可以把服务器端放到别的电脑上,客户端代码改成:Ice::ObjectPrx base =ic->stringToProxy("SimplePrinter:default -h
服务端IP -p 10000");即可实现远程调用。

看上去我们写一个Helloworld的程序要弄这么一大堆的东西,不过实际上只要我们修改Slice定义,我们就可实现更强大的功能,而代码并不需要多大变化。

使用Ice::Application简化代码的编写

对比上例中的服务端和客户端代码,可以发现占很大比例的代码都是初始化、异常捕捉、回收资源这样的“样板”代码。ICE针对这些“样板”代码提供了Ice::Application类来封装它们(而且它做得更多),通过它我们就可以简化上例中了代码了。

Ice::Application中有一个纯虚函数

virtual int run(int, char*[]) = 0;


我们只要实现这个run方法,其它的一切都由Application完成:

服务器端:

1. #include 
2. #include "printer.h"
3.  
4. using namespace std;
5. using namespace Demo;
6.  
7. struct PrinterImp : Printer{
8.     virtual void printString(const ::std::string& s, const ::Ice::Current& = ::Ice::Current())
9.     {
10.         cout << s << endl;   
11.     }
12. };
13.  
14. class MyApp : public Ice::Application{
15. public:
16.     virtual int run(intchar*[]){
17.         Ice::CommunicatorPtr& ic = communicator();
18.         Ice::ObjectAdapterPtr adapter
19.             = ic->createObjectAdapterWithEndpoints(
20.                 "SimplePrinterAdapter", "default -p 10000");
21.         Ice::ObjectPtr object = new PrinterImp;
22.         adapter->add(object, ic->stringToIdentity("SimplePrinter"));
23.         adapter->activate();
24.         ic->waitForShutdown();
25.         return 0;
26.     }
27. };
28.  
29. int main(int argc, char* argv[])
30. {
31.     MyApp app;
32.     return app.main(argc, argv);
33. }

原来的版本我们的退出方法只能使用很野蛮的强行退出,现在,服务端可以检测到Ctrl+C这样的退出信号了。

客户端:

1. #include 
2. #include 
3.  
4. using namespace std;
5. using namespace Demo;
6.  
7. class MyApp: public Ice::Application{
8. public:
9.     virtual int run(intchar*[])
10.     {
11.         Ice::CommunicatorPtr ic = communicator();
12.         Ice::ObjectPrx base = ic->stringToProxy("SimplePrinter:default -p 10000");
13.         PrinterPrx printer =  PrinterPrx::checkedCast(base);
14.         if(!printer) throw "Invalid Proxy!";
15.         printer->printString("Hello World!");
16.     }
17. };
18.  
19. int main(int argc, char* argv[])
20. {
21.     MyApp app;
22.     return app.main(argc,argv);
23. }

 

 

谈谈个人对ice的理解,它提供了一个客户端和服务端通信的功能,并且提供了一个开发模式,即在客户端和服务端配置好映射文件后,客户端就可以直接调用服务端的程序了,不必考虑通信的问题,ice会帮你自动去做通信处理。

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ice