ICE是ZeroC公司开发的一款高效的开源中间件平台,全称是Internet Communications Engine。
它的主要设计目标是:
? 提供适用于异种环境的面向对象中间件平台。
? 提供一组完整的特性,支持广泛的领域中的实际的分布式应用的开发。
? 避免不必要的复杂性,使平台更易于学习和使用。
? 提供一种在网络带宽、内存使用和 CPU 开销方面都很高效的实现。
? 提供一种具有内建安全性的实现,使它适用于不安全的公共网络。
ICE支持多种编程语言:C++、Java、C#、VB、Python、Ruby,也就是说使用ICE时我们可以让这些语言无缝沟通,不过由于ICE是用C++编写的,不管用什么语言,你都需要先用C++编译出一个ICE才行(或者下载已编译的版本)。
本篇文章以C++语言作为演示语言,其它语言除语法不同外,使用方法非常类似。
配置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
mcpp2. 编译完上面这些库以后,把它们放到同一个目录中,然后设置环境变量THIRDPARTY_HOME:
set THIRDPARTY_HOME = d:\ice3party3. 打开$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(Specificatoin Language for Ice)的语法,Slice语法和C++(或Java,C#)比较相近,只要会C++(或Java,C#)很容易就能写Slice定义了
下面是一个简单的接口的Slice定义:
module Demo {
interface Printer {
void printString(string s);
};
};
它定义一个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<T> std::vector<T>
dictionary<Key,Value> std::map<Key,Value>
exception Err class Err:public Ice:UserException
nonmutating方法限定符 const方法
idempotent方法限定符 -
out 参数限定符 引用类型
* 对应类型的代理类
参考这个表,可以知道上面的Slice定义对应的C++映射如下:
namespace Demo {
struct Printer {
virtual void printString(string s) = 0;
};
};
(严格地说,C++中对应的Printer类还继承自IceProxy::Ice::Object,目前我们可以不理这个问题)
我们只要在服务器端实现这个printString方法,就可以在客户端简单地调用它了。
编写服务器端代码:
新建一个控制台项目
将$ICE\include添加到头文件目录列表中
将$ICE\lib\ice.lib和iceutil.lib(对应的Debug版本是iced.lib和iceutild.lib)链接入项目
把生成Printer.cpp加入项目
#include <ice/ice.h>
#include "printer.h" //slice2cpp生成的文件
using namespace std;
using namespace Demo;
//实现printString方法
struct PrinterImp : Printer{
virtual void printString(const ::std::string& s,
const ::Ice::Current& = ::Ice::Current())
{
cout << s << endl;
}
};
int main(int argc, char* argv[])
{
Ice::CommunicatorPtr ic;
try{
// 初始化Ice运行库
ic = Ice::initialize(argc, argv);
// 建立ObjectAdapter,命名为SimplePrinterAdapter
// 使用默认协议(一般是tcp)并在10000端口监听。
Ice::ObjectAdapterPtr adapter
= ic->createObjectAdapterWithEndpoints(
"SimplePrinterAdapter", "default -p 10000");
// 把我们实现的Printer加入ObjectAdapter,并命名为SimplePrinter
Ice::ObjectPtr object = new PrinterImp;
adapter->add(object, ic->stringToIdentity("SimplePrinter"));
adapter->activate();
// 等待直到Communicator关闭
ic->waitForShutdown();
}
catch(const Ice::Exception &e){
cerr << e << endl;
}
catch(const char* msg){
cerr << msg << endl;
}
// 回收Ice运行库所用的资源
if(ic) ic->destroy();
return 0;
}
客户端代码:
#include <ice/ice.h>
#include <printer.h>
using namespace std;
using namespace Demo;
int main(int argc, char* argv[])
{
Ice::CommunicatorPtr ic;
try{
// 初始化Ice运行库
ic = Ice::initialize(argc, argv);
// 在10000端口取得SimplePrinter代理对象
Ice::ObjectPrx base = ic->stringToProxy("SimplePrinter:default -p 10000");
// 把对象转换成Printer代理
PrinterPrx printer = PrinterPrx::checkedCast(base);
if(!printer) throw "Invalid Proxy!";
// 能过这个代码调用printString方法
printer->printString("Hello World!");
}
catch(const Ice::Exception &e){
cerr << e << endl;
}
catch(const char* msg){
cerr << msg << endl;
}
// 回收Ice运行库所用的资源
if(ic) ic->destroy();
return 0;
}
编译服务器端和客户端,然后启动一个服务器端,每次调用客户端后服务器端会显示一行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完成:
服务器端:
#include <ice/ice.h>
#include "printer.h"
using namespace std;
using namespace Demo;
struct PrinterImp : Printer{
virtual void printString(const ::std::string& s, const ::Ice::Current& = ::Ice::Current())
{
cout << s << endl;
}
};
class MyApp : public Ice::Application{
public:
virtual int run(int, char*[]){
Ice::CommunicatorPtr& ic = communicator();
Ice::ObjectAdapterPtr adapter
= ic->createObjectAdapterWithEndpoints(
"SimplePrinterAdapter", "default -p 10000");
Ice::ObjectPtr object = new PrinterImp;
adapter->add(object, ic->stringToIdentity("SimplePrinter"));
adapter->activate();
ic->waitForShutdown();
return 0;
}
};
int main(int argc, char* argv[])
{
MyApp app;
return app.main(argc, argv);
}
原来的版本我们的退出方法只能使用很野蛮的强行退出,现在,服务端可以检测到Ctrl+C这样的退出信号了。
客户端:
#include <ice/ice.h>
#include <printer.h>
using namespace std;
using namespace Demo;
class MyApp: public Ice::Application{
public:
virtual int run(int, char*[])
{
Ice::CommunicatorPtr ic = communicator();
Ice::ObjectPrx base = ic->stringToProxy("SimplePrinter:default -p 10000");
PrinterPrx printer = PrinterPrx::checkedCast(base);
if(!printer) throw "Invalid Proxy!";
printer->printString("Hello World!");
}
};
int main(int argc, char* argv[])
{
MyApp app;
return app.main(argc,argv);
}