ICE中的基本概念

一) 名詞

ICE的語法規則

ICE的版本控制(Facet)

持久化 (Feeze)

服務裝箱管理 (ICEBox)

文件分發(ICEPatch2)

發佈/訂閱 服務(ICEStorm)

網絡拓撲負載解決方案--終極武器(ICEGrid)

提供使用安全傳輸入協議SSL的插件(IceSSL)

輕量級的ICE應用防火牆其解決方案(Galcier2)

二)開發流程

1)一般開發過程爲,先用ICE 接口描述語言寫好接口

2)使用工具將接口描述文檔轉成對應語言的代碼,轉出的代碼又分爲服務端客戶端代碼。

工具有以下幾種:

  • slice2cpp(轉成c++)
  • slice2freeze
  • slice2freezej
  • slice2html(轉成html)
  • slice2java(轉成java)
  • slice2php(轉成php)
  • sliceprint

3)用戶實現自己的需求,編寫實現類。(服務端、客戶端可以做成工具類)

三)文檔

https://doc.zeroc.com/display/Ice36/Ice+Manual

四)ICE的整體架構

(1)服務器端:

服務器端通常只有一個通信器(Ice::Communicator),通信器包含了一系列的資源:

線程池配置屬性對象工廠日誌記錄統計對象路由器定位器、插件管理器、對象適配器

在通信器內,包含有一個或更多對象適配器(Ice::ObjectAdapter),對象適配器負責提供一個或多個傳輸端點,並且把進入的請求分派到對應的servant(英文是職員,類似於java中的servlet)中去執行。

具體實現的部分稱爲servant,它們爲客戶端發來的調用提供服務servant向對象適配器註冊以後,由對象適配器依據客戶請求調用相應方法

(2)客戶端:

客戶端直接通過代理進行遠程調用,就象本地調用一樣簡單。

通信器Ice::Communicator

通信器管理着線程池、配置屬性、對象工廠、日誌記錄、統計對象、路由器、定位器、插件管理器、對象適配器。

通信器的幾個重要方法:

std::string proxyToString(const Ice::ObjectPrx&) const; (prx是proxy,代理的縮寫)
Ice::ObjectPrx stringToProxy(const std::string&) const;

這兩個方法可以使代理對象字符串之間互相轉換。對於proxyToString方法,你也可以使用代理對象的 ice_toString方法代替(當然,你要確保是非空的代理對象)。

Ice::ObjectPrx propertyToProxy(const std::string&) const;

這個方法根據給定名字的屬性配置生成一個代理對象,如果沒有對應屬性,返回一個空代理。
比如有如下屬性:

MyApp.Proxy = ident:tcp -p 5000

我們就可以這樣得到它的代理對象:

Ice::ObjectPrx p = communicator->propertyToProxy("MyApp.Proxy");

Ice::Identity stringToIdentity(const std::string&) const; 
std::string identityToString(const Ice::Identity&) const;

轉換字符串到一個對象標識(這是ice自帶的),對象標識的定義如下:

namespace Ice
{
    struct Identity
    {
        std::string name;
        std::string category;
    };
}

當它與字符串相互轉換時,對應的字符串形式是:CATEGORY/NAME(分類/對象名稱)。 比如字符串“Factory/File”, Factory是category,File是name。

category部分可以爲空。   

Ice::ObjectAdapterPtr createObjectAdapter(const std::string&); 
Ice::ObjectAdapterPtr createObjectAdapterWithEndpoints( const std::string&, const std::string&);

這兩個方法創建新的對象適配器。createObjectAdapter從屬性配置中取得端點信息,而 createObjectAdapterWithEndpoints則直接指定端點。

void shutdown();

關閉服務端的Ice運行時庫,調用shutdown後,執行過程中的操作仍可正常完成,shutdown不會等待這些操作完成

void waitForShutdown();

這個方法會掛起發出調用的線程直到通信器關閉爲止。

void destroy();

這個方法回收通信器的相關資源,如線程、通信端點及內存資源。在離開main函數之前,必須調用destory。

bool isShutdown() const;

如果shutdown已被調用過,則返回true。

初始化通信器

       在建立通信器(Ice::Communicator)期間,Ice運行時會初始化一系列的對象,這些對象一直影響通信器的整個生命週期。並且在建立通信器以後,你不能改變這些對象。所以,如果你想定製這些對象,就必須在建立通信器的過程中定義

       在通信器建立期間,我們可以定義下面這些對象:

  • 屬性表(property)
  • 日誌記錄器(Logger)
  • 統計對象(Stats)
  • 原生字符串與寬字符串轉換器
  • 線程通知鉤子

    所有上面的對象存放在InitializationData結 構中,定義爲:

namespace Ice {
    struct InitializationData {
        PropertiesPtr properties;
        LoggerPtr logger;
        StatsPtr stats;
        StringConverterPtr stringConverter;
        WstringConverterPtr wstringConverter;
        ThreadNotificationPtr threadHook;
    };
}

這個結構中的所有成員都是智能指針類型,設置好這些成員以後,就可以通過通信器的初始化函數傳入這些對象:

namespace Ice {
    CommunicatorPtr initialize(int&, char*[],
                const InitializationData& = InitializationData());
    CommunicatorPtr initialize(StringSeq&,
                const InitializationData& = InitializationData());
    CommunicatorPtr initialize(
                const InitializationData& = InitializationData());
}

 我們前面使用的Ice::Application也提供了InitializationData的傳入途徑:

namespace Ice
{
    struct Application
    {
        int main(int, char*[]);
        int main(int, char*[], const char*);
        int main(int, char*[], const Ice::InitializationData&);
        int main(const StringSeq&);
        int main(const StringSeq&, const char*);
        int main(const StringSeq&, const Ice::InitializationData&);
        ...
    };
}

再回頭看InitializationData結構:

    properties:PropertiesPtr 類型,指定了屬性表(property)對象,它就是之前《Ice屬性配置》一文中的主角。默認的屬性表實現可以解析“Key = Value”這種形式的字符串(包括命令行參數和文件),如果願意,你可以自己寫一個屬性表實現,用來解析xml、ini等等。

    如果要自己實現,就得完成下面這些接口(每個方法的作用請參考《Ice屬性配置》):

namespace Ice
{
    class Properties : virtual public Ice::LocalObject
    {
        public:
        virtual std::string getProperty(const std::string&) = 0;
        virtual std::string getPropertyWithDefault(const std::string&,
            const std::string&) = 0;
        virtual Ice::Int getPropertyAsInt(const std::string&) = 0;
        virtual Ice::Int getPropertyAsIntWithDefault(const std::string&,
            Ice::Int) = 0;
        virtual Ice::StringSeq getPropertyAsList(const std::string&) = 0;
        virtual Ice::StringSeq getPropertyAsListWithDefault(const std::string&,
            const Ice::StringSeq&) = 0;
        virtual Ice::PropertyDict getPropertiesForPrefix(const std::string&) = 0;
        virtual void setProperty(const std::string&, const std::string&) = 0;
        virtual Ice::StringSeq getCommandLineOptions() = 0;
        virtual Ice::StringSeq parseCommandLineOptions(const std::string&,
            const Ice::StringSeq&) = 0;
        virtual Ice::StringSeq parseIceCommandLineOptions(const Ice::StringSeq&) = 0;
        virtual void load(const std::string&) = 0;
        virtual Ice::PropertiesPtr clone() = 0;
    };
};

    logger: LoggerPtr類型,這是一個日誌記錄器接口,它可以記錄Ice運行過程中產生的跟蹤、警告和錯誤信息,默認實現是直接向cerr輸出。比如作用我們 之前的Helloworld的例子,在沒開服務端的情況下運行客戶端,就看到在控制超臺上打印了一串錯誤信息。

    我們可以自己實現這個接口,以控制它的輸出方向,它的定義爲:

namespace Ice
{
    class Logger : virtual public Ice::LocalObject
    {
    public:
        virtual void print(const std::string& msg) = 0;
        virtual void trace(const std::string& category,
            const std::string& msg) = 0;
        virtual void warning(const std::string& msg) = 0;
        virtual void error(const std::string& msg) = 0;
    };
}

 不用說,實現它們是一件很輕鬆的事情^_^,比如你可以實現這個接口把信息寫到一個日誌文件裏,或者把它寫到某個日誌服務器上。

    stats: StatsPtr類型,當Ice發送或接收到數據時,會向Stats報告發生的字節數,這個接口更加簡單:

namespace Ice
{
class Stats : virtual public Ice::LocalObject
{
public:
    virtual void bytesSent(const std::string& protocol,
        Ice::Int num) = 0;
    virtual void bytesReceived(const std::string& protocol,
        Ice::Int num) = 0;
};
}

    stringConverter:BasicStringConverter<char> 類型;
    wstringConverter:BasicStringConverter<wchar_t> 類型;

    這兩個接口用於本地編碼與UTF-8編碼之間的轉換,Ice系統自帶了三套轉換系統,默認的UnicodeWstringConverter、Linux/Unix 下使用的IconvStringConverter和Windows 下使用的WindowsStringConverter。

    threadHook: ThreadNotificationPtr類型,線程通知鉤子,當Ice建立一個新線程後,線程通知鉤子就會首先得到“線程啓動通知”,在結束線程之 前,也能得到“線程結束通知”。

    下面是ThreadNotification接口的定義:

namespace Ice
{
class ThreadNotification : public IceUtil::Shared {
public:
    virtual void start() = 0;
    virtual void stop() = 0;
};
}

    假如我們在Windows下使用了COM組件的話,就可以使用線程通知鉤子在start和stop裏調用 CoInitializeEx和CoUninitialize。

代碼演示

修改一下Helloworld服 務器端代碼,實現自定義統計對象(Stats,畢竟它最簡單嘛 -_-):

#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&)
    {
        cout << s << endl;    
    }
};

class MyStats : public Ice::Stats {
public:
    virtual void bytesSent(const string &prot, Ice::Int num)
    {
        cerr << prot << ": sent " << num << "bytes" << endl;
    }
    virtual void bytesReceived(const string &prot, Ice::Int num)
    {
        cerr << prot << ": received " << num << "bytes" << endl;
    }
};

class MyApp : public Ice::Application{
public:
    virtual int run(int n, char* v[]){
        Ice::CommunicatorPtr& ic = communicator();
        ic->getProperties()->parseCommandLineOptions(
            "SimplePrinterAdapter", Ice::argsToStringSeq(n,v));
        Ice::ObjectAdapterPtr adapter
            = ic->createObjectAdapter("SimplePrinterAdapter");
        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;
    Ice::InitializationData id;
    id.stats = new MyStats;

    return app.main(argc, argv, id);
}

編譯運行這個演示代碼,然後執行客戶端,可以看到打印出的接收到發送字符數。

tcp: send 14bytes tcp: received 14bytes tcp: received 52bytes tcp: send 26bytes tcp: received 14bytes tcp: received 53bytes Hello World! tcp: send 25bytes tcp: received 14bytes

對象適配器(Ice::ObjectAdapter)

對象適配器負責提供一個或多個傳輸端點,並且把進入的請求分派到對應的servant中去執行。它的定義以及主要方法有:

namespace Ice
{
struct ObjectAdapter :  public LocalObject
{
    // 返回適配器的名字(由 Communicator::createObjectAdapter輸入)
    std::string getName() const;
    // 返回創建並擁有應適配器的通信器
    Ice::CommunicatorPtr getCommunicator() const;
    // 激活處於hold狀態的適配器。
    void activate();
    // 要求適配器進入hold狀態,並馬上 返回
    void hold();
    // 等待直到適配器進入hold狀態
    void waitForHold();
    // 要求適配器進入無效狀態,一旦進入無 效狀態,就無法再次激活它,與該適配器關聯的servant將會被銷燬。
    void deactivate();
    // 等待直到適配器進入無效狀態    
    void waitForDeactivate();
    // 如果適配器處於無效狀態,返回 true    
    bool isDeactivated() const;
    // 使適配器處於無效狀態,並且釋放所有 的資源(包括適配器名)
    void destroy();
    // 使用指定的標識把servant註冊 到適配器中,返回該servant的代理
    Ice::ObjectPrx add(const Ice::ObjectPtr&, const Ice::Identity&);
    // 使用隨機生成的UUID作爲標識把 servant註冊到適配器中,返回該servant的代理
    Ice::ObjectPrx addWithUUID(const Ice::ObjectPtr&);
    // 從適配器中移除對應的servant
    Ice::ObjectPtr remove(const Ice::Identity&);
    // 查找對應標識的servant
    Ice::ObjectPtr find(const Ice::Identity&) const;
    // 查找代理對應的servant
    Ice::ObjectPtr findByProxy(const Ice::ObjectPrx&) const;
    // 把一 個 Servant Locator 添加到這個對象適配器中。
    void addServantLocator(const Ice::ServantLocatorPtr&, const std::string&);
    // 查找一個已經安裝到這個對象適配器中 的 Servant Locator。
    Ice::ServantLocatorPtr findServantLocator(const std::string&) const;
    // 創建一個與這個對象適配器及給定標識 相匹配的代理
    Ice::ObjectPrx createProxy(const Ice::Identity&) const;
    // 創建一個與這個對象適配器及給定標識 相匹配的 " 直接代理 "。
    // 直接代理總是包含有當前的適配器端點。
    Ice::ObjectPrx createDirectProxy(const Ice::Identity&) const;
    // 創建一個與這個對象適配器及給定標識相匹配的 " 間接代 理 "。
    // 間接代理只包含對象的標識和適配器 名,通過定位器服務來得到服務器地址
    Ice::ObjectPrx createIndirectProxy(const Ice::Identity&) const;
    // 爲這個對象適配器設置一 個 Ice 定位器
    void setLocator(const Ice::LocatorPrx&);
    // 重新讀入適配器的 PublicshedEndpoints屬性並更新內部的可用網絡接口列表
    void refreshPublishedEndpoints();
};
}

Servant定位器

除直接向對象適配器註冊servant以外,Ice允許我們向對象適配器提供一個Servant定位器

有了Servant定位器以後,對象適配器得到一次請求時首先查找已註冊的servant,如果沒找到對應的servant,就會請求 Servant定位器提供servant。

採用這種簡單的機制,我們的服務器能夠讓我們訪問數量不限的servant:服務器不必爲每一個現有的Ice對象實例化一個單獨的 servant。

下面是Servant定位器的接口,我們必須自己實現這個接口:

namespace Ice
{
class ServantLocator : virtual public Ice::LocalObject
{
    // 只要有請求到達,而且適配器沒有提供 註冊的條目,Ice就會調用locate。
    // locate的實現(由你在派生類中提供)應該返回一個能夠處 理該請求的 servant。
    // 通過cookie參數,你可以傳入一 個自定義指針數據,對象適配器並不在乎這個對象的內容
    // 當Ice 調用finished 時,會把你從 locate 返回的cookie傳回給你。
    virtual Ice::ObjectPtr locate(const Ice::Current& curr,
        Ice::LocalObjectPtr& cookie) = 0;
    // 一旦請求完成,Ice就會調用 finished。
    // 把完成了操作的servant、該請求的Current 對象
    // 以及locate在一開始創建 的 cookie 傳給它。
    virtual void finished(const Ice::Current& curr,
        const Ice::ObjectPtr& obj, const Ice::LocalObjectPtr& cookie) = 0;
    // 當servant定位器所屬的對象適配器無效時,Ice會調用 deactivate方法。
    virtual void deactivate(const std::string&) = 0;
};
}

實現了ServantLocator後,通過對象適配器的addServantLocator方法註冊到該適配器中。

在上面的接口中,有一個Ice::Current結構類型的參數,通過它,我們就可以訪問“正在執行的請求”和“服務器中的操作的實現”等信息,Ice::Current的 定義如下:

namespace Ice
{
struct Current
{
    // 負責分派當前請求的對象適配器
    Ice::ObjectAdapterPtr adapter;
    //
    Ice::ConnectionPtr con;
    // 當前請求的對象標識
    Ice::Identity id;
    // 請求的 facet
    std::string facet;
    // 正在被調用的操作的名字。
    std::string operation;
    // 操作的調用模式(Normal、 Idempotent,或Nonmutating)
    Ice::OperationMode mode;
    // 這個調用的當前上下文,這是一個 std::map類型的數據
    // 使用它就允許把數量不限的參數從客戶發往服務器
    Ice::Context ctx;
    //
    Ice::Int requestId;
};
}

利用Ice::Current的id成員,我們可以得到所請求的對象標識,從而決定生產某個具體的 Servant;使用ctx成員,我們還可以從客戶端發送數據不限的 “鍵值-數值”對到服務器中,實現靈活控制(甚至連方法參數都可以不用了,全部用ctx轉送就行)。

例:向原HelloWorld 版服務器加入Servant定位器代碼,當請求的標識的category爲"Loc"時,由定位器返回對應的 servant。

服務器端

#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&)
    {
        cout << s << endl;    
    }
};

// OutputDebugString版本
struct DbgOutputImp : Printer{
    virtual void printString(const ::std::string& s, const ::Ice::Current&)
    {
        ::OutputDebugStringA(s.c_str());
    }
};

// MessageBox版本
struct MsgboxImp : Printer{
    virtual void printString(const ::std::string& s, const ::Ice::Current&)
    {
        ::MessageBoxA(NULL,s.c_str(),NULL,MB_OK);
    }
};


struct MyLocator : Ice::ServantLocator{
    virtual Ice::ObjectPtr locate(const Ice::Current& curr,
        Ice::LocalObjectPtr& cookie)
    {
        if(curr.id.name == "Dbg")
            return Ice::ObjectPtr(new DbgOutputImp);
        else if(curr.id.name == "Msg")
            return Ice::ObjectPtr(new MsgboxImp);
        else if(curr.id.name == "SimplePrinter")
            return Ice::ObjectPtr(new PrinterImp);
        else
            return NULL;
    }

    virtual void finished(const Ice::Current& curr,
        const Ice::ObjectPtr& obj, const Ice::LocalObjectPtr&)
    {
    }

    virtual void deactivate(const std::string& category)
    {
    }
};

class MyApp : public Ice::Application{
public:
    virtual int run(int n, char* v[]){
        Ice::CommunicatorPtr& ic = communicator();
        ic->getProperties()->parseCommandLineOptions(
            "SimplePrinterAdapter", Ice::argsToStringSeq(n,v));
        Ice::ObjectAdapterPtr adapter
            = ic->createObjectAdapter("SimplePrinterAdapter");
        Ice::ObjectPtr object = new PrinterImp;
        adapter->add(object, ic->stringToIdentity("SimplePrinter"));

        // 註冊MyLocator定位器,負責返回 category爲"Loc"的相應servant。
        adapter->addServantLocator(Ice::ServantLocatorPtr(new MyLocator),"Loc");
        adapter->activate();
        ic->waitForShutdown();
        return 0;
    }
};

int main(int argc, char* argv[])
{
    MyApp app;
    return app.main(argc, argv);
}

客戶端代碼與《ICE屬性配置》 中的代碼相同,客戶端命令行參數爲

--MyProp.Printer="Loc/Msg:tcp -p 10000"

服務器端將會使用對話框顯示“HelloWorld”。

對象代理(Object Proxy)

    在客戶端,我們使用對象代理進行遠程調用,就如它們就在本地一樣。但有時,網絡問題還是要考慮的,於是Ice的對象代理提供了幾個包裝方法,以支持一些網絡特性:

ice_timeout方法,聲明爲:Ice::ObjectPrx ice_timeout(int) const;返回一個超時代理,當在指定的 時間(單位毫秒)內沒有得到服務器端響應時,操作終止並拋出Ice::TimeoutException異常。

示例代碼:

Filesystem::FilePrx myFile = ...;
FileSystem::FilePrx timeoutFile
= FileSystem::FilePrx::uncheckedCast(
        myFile->ice_timeout(5000));
try {
    Lines text = timeoutFile->read(); // Read with timeout
} catch(const Ice::TimeoutException &) {
    cerr << "invocation timed out" << endl;
}
Lines text = myFile->read(); // Read without timeout

ice_oneway方法,聲明爲:Ice::ObjectPrx ice_oneway() const;返回一個單向調用代理只要數據從本地端口發送出去,單向調用代理就認爲已經調用成功。這意味着,單向調用是不可靠的:它可能根本沒有發送出去 (例如,因爲網絡故障) ,也可能沒有被服務器接受(例如,因爲目標對象不存在)。好處是由於不用等服務端回覆,能帶來很大的效率提升。

示例代碼:

Ice::ObjectPrxo=communicator->stringToProxy();
// Get a oneway proxy.
Ice::ObjectPrx oneway = o->ice_oneway();

// Down-cast to actual type.
PersonPrx onewayPerson = PersonPrx::uncheckedCast(oneway);
// Invoke an operation as oneway.
try {
    onewayPerson->someOp();
} catch (const Ice::TwowayOnlyException &) {
    cerr << "someOp() is not oneway" << endl;
}

ice_datagram方法,聲明爲:Ice::ObjectPrx ice_datagram() const;返回數據報代理它使用UDP傳輸機制,並且和單向調用代理一樣,不會得到服務器端的答覆,而且還有可能UDP包重複和不按次序到達服務端。示例代碼:

Ice::ObjectPrxo=communicator->stringToProxy();
// Get a datagram proxy.
//
Ice::ObjectPrx datagram;
try {
    datagram = o->ice_datagram();
} catch (const Ice::NoEndPointException &) {
    cerr << "No endpoint for datagram invocations" << endl;
}
// Down-cast to actual type.
//
PersonPrx datagramPerson = PersonPrx::uncheckedCast(datagram);
// Invoke an operation as a datagram.
//
try {
    datagramPerson->someOp();
} catch (const Ice::TwowayOnlyException &) {
    cerr << "someOp() is not oneway" << endl;
}

批量調用代理:

Ice::ObjectPrx ice_batchOneway() const;  
Ice::ObjectPrx ice_batchDatagram() const;  
void ice_flushBatchRequests();

爲了提供網絡效率,對於單向調用,可以考慮把多個調用打包一起送往服務器,Ice對象代理提供了ice_batchOneway和ice_batchDatagram方法返回對應的批調用代理,使用這種代理時呼叫信息不會馬上發出,而是等到調用ice_flushBatchRequests以後才一次性發出。

示例代碼:

Ice::ObjectPrx base = ic->stringToProxy(s);
PrinterPrx printer =  PrinterPrx::uncheckedCast(base->ice_batchOneway());
if(!printer) throw "Invalid Proxy!";
printer->printString("Hello");
printer->printString("World");
printer->ice_flushBatchRequests();

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章