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();
- } catch ( const Ice::TimeoutException &) {
- cerr << "invocation timed out" << endl;
- }
- Lines text = myFile->read();
ice_oneway方法 ,声明为:Ice::ObjectPrx ice_oneway() const; 返回一个单向调用代理。只要数据从本地端口发送出去,单向调用代理就认为已经调用成功。这意味着,单向调用是不可靠的:它可能根本没有发送出去(例如,因为网络故障) ,也可能没有被服务器接受(例如,因为目标对象不存在)。好处是由于不用等服务端回复,能带来很大的效率提升。
示例代码 :
- Ice::ObjectPrxo=communicator->stringToProxy( );
-
- Ice::ObjectPrx oneway = o->ice_oneway();
-
-
- PersonPrx onewayPerson = PersonPrx::uncheckedCast(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( );
-
-
- Ice::ObjectPrx datagram;
- try {
- datagram = o->ice_datagram();
- } catch ( const Ice::NoEndPointException &) {
- cerr << "No endpoint for datagram invocations" << endl;
- }
-
-
- PersonPrx datagramPerson = PersonPrx::uncheckedCast(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();