用ICE開發分佈式程序

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
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(Specificatoin Language 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<T> std::vector<T>
dictionary<Key,Value> std::map<Key,Value>
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 <ice/ice.h>
  2. #include "printer.h"  //slice2cpp生成的文件
  3. using namespace std;
  4. using namespace Demo;
  5. //實現printString方法
  6. struct PrinterImp : Printer{
  7.     virtual void printString(const ::std::string& s,
  8.         const ::Ice::Current& = ::Ice::Current())
  9.     {
  10.         cout << s << endl;   
  11.     }
  12. };
  13. int main(int argc, char* argv[])
  14. {
  15.     Ice::CommunicatorPtr ic;
  16.     try{
  17.         // 初始化Ice運行庫
  18.         ic = Ice::initialize(argc, argv);
  19.         // 建立ObjectAdapter,命名爲SimplePrinterAdapter
  20.         // 使用默認協議(一般是tcp)並在10000端口監聽。
  21.         Ice::ObjectAdapterPtr adapter
  22.             = ic->createObjectAdapterWithEndpoints(
  23.                 "SimplePrinterAdapter""default -p 10000");
  24.         // 把我們實現的Printer加入ObjectAdapter,並命名爲SimplePrinter
  25.         Ice::ObjectPtr object = new PrinterImp;
  26.         adapter->add(object, ic->stringToIdentity("SimplePrinter"));
  27.         adapter->activate();
  28.         // 等待直到Communicator關閉
  29.         ic->waitForShutdown();
  30.     }
  31.     catch(const Ice::Exception &e){
  32.         cerr << e << endl;
  33.     }
  34.     catch(const char* msg){
  35.         cerr << msg << endl;
  36.     }
  37.     // 回收Ice運行庫所用的資源
  38.     if(ic) ic->destroy();
  39.    
  40.     return 0;
  41. }

客戶端代碼:

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

編譯服務器端和客戶端,然後啓動一個服務器端,每次調用客戶端後服務器端會顯示一行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 <ice/ice.h>
  2. #include "printer.h"
  3. using namespace std;
  4. using namespace Demo;
  5. struct PrinterImp : Printer{
  6.     virtual void printString(const ::std::string& s, const ::Ice::Current& = ::Ice::Current())
  7.     {
  8.         cout << s << endl;   
  9.     }
  10. };
  11. class MyApp : public Ice::Application{
  12. public:
  13.     virtual int run(intchar*[]){
  14.         Ice::CommunicatorPtr& ic = communicator();
  15.         Ice::ObjectAdapterPtr adapter
  16.             = ic->createObjectAdapterWithEndpoints(
  17.                 "SimplePrinterAdapter""default -p 10000");
  18.         Ice::ObjectPtr object = new PrinterImp;
  19.         adapter->add(object, ic->stringToIdentity("SimplePrinter"));
  20.         adapter->activate();
  21.         ic->waitForShutdown();
  22.         return 0;
  23.     }
  24. };
  25. int main(int argc, char* argv[])
  26. {
  27.     MyApp app;
  28.     return app.main(argc, argv);
  29. }

原來的版本我們的退出方法只能使用很野蠻的強行退出,現在,服務端可以檢測到Ctrl+C這樣的退出信號了。

客戶端:

  1. #include <ice/ice.h>
  2. #include <printer.h>
  3. using namespace std;
  4. using namespace Demo;
  5. class MyApp: public Ice::Application{
  6. public:
  7.     virtual int run(intchar*[])
  8.     {
  9.         Ice::CommunicatorPtr ic = communicator();
  10.         Ice::ObjectPrx base = ic->stringToProxy("SimplePrinter:default -p 10000");
  11.         PrinterPrx printer =  PrinterPrx::checkedCast(base);
  12.         if(!printer) throw "Invalid Proxy!";
  13.         printer->printString("Hello World!");
  14.     }
  15. };
  16. int main(int argc, char* argv[])
  17. {
  18.     MyApp app;
  19.     return app.main(argc,argv);
  20. }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章