ICE的整體架構
服務器端:
服務器端通常只有一個通信器(Ice::Communicator),通信器包含了一系列的資源:
如線程池、配置屬性、對象工廠、日誌記錄、統計對象、路由器、定位器、插件管理器、對象適配器
在通信器內,包含有一個或更多的對象適配器(Ice::ObjectAdapter),對象適配器負責提供一個或多個傳輸端點,並且把進入的請求分派到對應的servant中去執行。
具體實現的部分稱爲servant,它們爲客戶端發來的調用提供服務。servant向對象適配器註冊以後,由對象適配器依據客戶請求調用相應方法。
客戶端:
客戶端直接通過代理進行遠程調用,就象本地調用一樣簡單。
通信器Ice::Communicator
通信器管理着線程池、配置屬性、對象工廠、日誌記錄、統計對象、路由器、定位器、插件管理器、對象適配器。
通信器的幾個重要方法:
std::string proxyToString (const Ice::ObjectPrx&) const; 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;
轉換字符串到一個對象標識,對象標識的定義如下:
- 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
對象代理(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();