一) 名詞
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();