ICE是ZeroC公司開發(fā)的一款高效的開源中間件平臺,全稱是Internet Communications Engine。
它的主要設(shè)計(jì)目標(biāo)是:
提供適用于異種環(huán)境的面向?qū)ο笾虚g件平臺。
提供一組完整的特性,支持廣泛的領(lǐng)域中的實(shí)際的分布式應(yīng)用的開發(fā)。
避免不必要的復(fù)雜性,使平臺更易于學(xué)習(xí)和使用。
提供一種在網(wǎng)絡(luò)帶寬、內(nèi)存使用和 CPU 開銷方面都很高效的實(shí)現(xiàn)。
提供一種具有內(nèi)建安全性的實(shí)現(xiàn),使它適用于不安全的公共網(wǎng)絡(luò)。
ICE支持多種編程語言:C++、Java、C#、VB、Python、Ruby,也就是說使用ICE時我們可以讓這些語言無縫溝通,不過由于ICE是用C++編寫的,不管用什么語言,你都需要先用C++編譯出一個ICE才行(或者下載已編譯的版本)。
本篇文章以C++語言作為演示語言,其它語言除語法不同外,使用方法非常類似。
配置ICE開發(fā)環(huán)境
首先,從http://www./download.html 下載ICE,目前最新版本是Ice-3.3.1。下載頁面里除了ICE的源碼之外,也提供了VC或C++Builder的已編譯安裝包以及各Linux版本的RPM下載。
如果下載的是源碼版本,編譯方法是(以VC2005 Express為例):
1. ICE需要一些第三方庫,在編譯ICE之前要先編譯第三方庫,清單如下(它們也能在ICE官網(wǎng)上下載):
Berkeley DB
expat
OpenSSL
bzip2
mcpp
2. 編譯完上面這些庫以后,把它們放到同一個目錄中,然后設(shè)置環(huán)境變量THIRDPARTY_HOME:
set THIRDPARTY_HOME = d:\ice3party
3. 打開$ICE/cpp/Make.rules.mak,找到CPP_COMPILER變量,改成CPP_COMPILER = VC80_EXPRESS
這個依據(jù)你的編譯器決定,可以是VC60, VC80, VC80_EXPRESS, VC90, VC90_EXPRESS, BCC2007, BCC2009
4. 如果想讓編譯的庫能脫離VC運(yùn)行時庫,打開$ICE/cpp/Make.rules.msvc,把CPPFLAGS = $(CPPFLAGS) -MD改成CPPFLAGS = $(CPPFLAGS) -MT。
5. 在命令行下進(jìn)入$ICE/cpp目錄,輸入nmake -f Makefile.mak開始編譯。默認(rèn)是編譯成Debug模式的DLL庫。如果想編譯成靜態(tài)庫,可以設(shè)置變量STATICLIBS=yes;想編譯成Release模式,設(shè)置OPTIMIZE=yes。如
nmake -f Makefile.mak STATICLIBS=yes OPTIMIZE=yes
如果按上面方法設(shè)置,應(yīng)該不會有問題。
6. 最最后,把bin目錄加入path變量,以便讓系統(tǒng)能找到ICE的dll文件(
其實(shí)主要是三個dll文件,ice33.dll、iceutil33.dll和bzip2.dll)
以后編譯ICE的程序時,把上面編譯好或直接下載的已編譯版本的ice.lib和iceutil.lib(或Debug版本的iced.lib和iceutild.lib)鏈接入項(xiàng)目即可。
ICE的HelloWorld
跨語言的分布式系統(tǒng)首先要定義一個與編程語言無關(guān)的接口描述語法,用于分布于各處的服務(wù)器與客戶端之間對話。比如DCOM和CORBA使用IDL語法,SOAP使用WSDL語法,當(dāng)然還有時下流行的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方法,輸入?yún)?shù)是一個字符串(string)。最后,這個接口位于Demo模塊(module)之下。
把它保存為Printer.ice后接著我們使用slice2cpp程序依據(jù)這個Slice定義生成C++使用的頭文件和對應(yīng)的代理代碼:
slice2cpp Printer.ice
如果沒提示錯誤,就會生成Printer.h和Printer.cpp,把這兩個文件加入到服務(wù)器端項(xiàng)目和客戶端項(xiàng)目后就可以互相對話了。
下表是Slice與C++的映射關(guān)系
| 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(不支持指定數(shù)字) |
| struct |
struct |
| class |
class(所有方法都是純虛函數(shù)) |
| interface |
struct(所有方法都是純虛函數(shù),沒有成員變量) |
| sequence |
std::vector |
| dictionary |
std::map |
| exception Err |
class Err:public Ice:UserException |
| nonmutating方法限定符 |
const方法 |
| idempotent方法限定符 |
- |
| out 參數(shù)限定符 |
引用類型 |
| * |
對應(yīng)類型的代理類 |
參考這個表,可以知道上面的Slice定義對應(yīng)的C++映射如下:
- namespace Demo {
- struct Printer {
- virtual void printString(string s) = 0;
- };
- };
(嚴(yán)格地說,C++中對應(yīng)的Printer類還繼承自IceProxy::Ice::Object,目前我們可以不理這個問題)
我們只要在服務(wù)器端實(shí)現(xiàn)這個printString方法,就可以在客戶端簡單地調(diào)用它了。
編寫服務(wù)器端代碼:
- 新建一個控制臺項(xiàng)目
- 將$ICE\include添加到頭文件目錄列表中
- 將$ICE\lib\ice.lib和iceutil.lib(對應(yīng)的Debug版本是iced.lib和iceutild.lib)鏈接入項(xiàng)目
- 把生成Printer.cpp加入項(xiàng)目
- #include
- #include "printer.h" //slice2cpp生成的文件
-
- using namespace std;
- using namespace Demo;
-
- //實(shí)現(xiàn)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運(yùn)行庫
- ic = Ice::initialize(argc, argv);
- // 建立ObjectAdapter,命名為SimplePrinterAdapter
- // 使用默認(rèn)協(xié)議(一般是tcp)并在10000端口監(jiān)聽。
- Ice::ObjectAdapterPtr adapter
- = ic->createObjectAdapterWithEndpoints(
- "SimplePrinterAdapter", "default -p 10000");
- // 把我們實(shí)現(xiàn)的Printer加入ObjectAdapter,并命名為SimplePrinter
- Ice::ObjectPtr object = new PrinterImp;
- adapter->add(object, ic->stringToIdentity("SimplePrinter"));
- adapter->activate();
- // 等待直到Communicator關(guān)閉
- ic->waitForShutdown();
- }
- catch(const Ice::Exception &e){
- cerr << e << endl;
- }
- catch(const char* msg){
- cerr << msg << endl;
- }
- // 回收Ice運(yùn)行庫所用的資源
- if(ic) ic->destroy();
-
- return 0;
- }
客戶端代碼:
- #include
- #include
-
- using namespace std;
- using namespace Demo;
-
- int main(int argc, char* argv[])
- {
- Ice::CommunicatorPtr ic;
- try{
- // 初始化Ice運(yùn)行庫
- ic = Ice::initialize(argc, argv);
- // 在10000端口取得SimplePrinter代理對象
- Ice::ObjectPrx base = ic->stringToProxy("SimplePrinter:default -p 10000");
- // 把對象轉(zhuǎn)換成Printer代理
- PrinterPrx printer = PrinterPrx::checkedCast(base);
- if(!printer) throw "Invalid Proxy!";
- // 能過這個代碼調(diào)用printString方法
- printer->printString("Hello World!");
- }
- catch(const Ice::Exception &e){
- cerr << e << endl;
- }
- catch(const char* msg){
- cerr << msg << endl;
- }
- // 回收Ice運(yùn)行庫所用的資源
- if(ic) ic->destroy();
-
- return 0;
- }
編譯服務(wù)器端和客戶端,然后啟動一個服務(wù)器端,每次調(diào)用客戶端后服務(wù)器端會顯示一行Hello world!
你也可以把服務(wù)器端放到別的電腦上,客戶端代碼改成:Ice::ObjectPrx base = ic->stringToProxy("SimplePrinter:default -h 服務(wù)端IP -p 10000");即可實(shí)現(xiàn)遠(yuǎn)程調(diào)用。
看上去我們寫一個Helloworld的程序要弄這么一大堆的東西,不過實(shí)際上只要我們修改Slice定義,我們就可實(shí)現(xiàn)更強(qiáng)大的功能,而代碼并不需要多大變化。
使用Ice::Application簡化代碼的編寫
對比上例中的服務(wù)端和客戶端代碼,可以發(fā)現(xiàn)占很大比例的代碼都是初始化、異常捕捉、回收資源這樣的“樣板”代碼。ICE針對這些“樣板”代碼提供了Ice::Application類來封裝它們(而且它做得更多),通過它我們就可以簡化上例中了代碼了。
Ice::Application中有一個純虛函數(shù)
virtual int run(int, char*[]) = 0;
我們只要實(shí)現(xiàn)這個run方法,其它的一切都由Application完成:
服務(wù)器端:
- #include
- #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);
- }
原來的版本我們的退出方法只能使用很野蠻的強(qiáng)行退出,現(xiàn)在,服務(wù)端可以檢測到Ctrl+C這樣的退出信號了。
客戶端:
- #include
- #include
-
- 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);
- }