软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用(oneway);回调是一种双向调用模式(twoway),也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。
估计这个大家都非常清楚,通俗的讲就是一个请求的发起方。
代理实际上就是远程服务驻在本地的一个代表,创建它时首先会和远程服务经行握手和状态确认等操作,Client所有的操作都是通过Proxy来办理的。代理又分为直接代理(已经知道服务端的位置及其他信息)和间接代理(不知道服务器在哪里,由Registry注册器告诉它地址等信息)。
Adapter是配置相当于服务单位(Servant)的管理者,它管理着每个请求该分配给哪一个服务单位。
它是服务的最小单位,一般是具体到 某个接口的实现。
ICE我们会经常提到了接口的实现,但是这个接口是谁定义的,这个时候我们不免要提到Slice(Specification Language for Ice),也就是ICE所使用的“语言”,正是有了这个“中间语言”,我们才可以做到支持各种编程语言,因为您所使用的语言只要跟Slice打交道就可以了。
图2.1 ICE体系结构
图2.2 ICE支持的平台
通过配置文件,我们可以设置Ice程序各个方面的应用,Ice在运行时能够识别配置文件中规定的属性集。Ice只会在创建通信器时读入属性配置。也就是说,若要设置属性的话,必须在创建通讯器以前设置,否则不起作用。
以#开头部分为注释。属性单词之间可以有空格。属性前、后空格自动清除,中间的空格会保留。
属性格式:<应用程序名>.<类别名>[.<子类别名>]。
以Ice,IceBox,IceGrid,IcePatch2,IceSSL,IceStorm,Freeze,Glacier2为前缀的属性专用于Ice运行时,不能用于用户程序和配置。
配置文件可通过环境变量ICE_CONFIG指定,也可以在命令行指定:
>server --Ice.Config=c:/Zeroc/config server.config
属性也可以在命令行上指定:
>server --Ice.MessageSizeMax=4096 --Ice.Config= c:/Zeroc/config
无论命令行指定的属性在前,还是在后,命令行指定的属性总是会覆盖配置文件中的同名属性。
>server --myoption --Ice.Config=server.config -x --Ice.Trace.Network=3 -y
当通讯器执行初始化语句:
// ...
Ice::CommunicatorPtr ic;
// ...
ic = Ice::initialize(argc, argv);
// ...
initialize返回时,argv中包含的形参向量为:--myoption –x –y;与Ice相关参数均被传给Ice运行时,并从形参向量中删除,留给应用程序的只是应用程序特定的参数向量。
Ice:Application的run函数具有类似功能,传入run函数的参数向量只含有应用程序特定的形参向量。Ice相关形参向量已在调用run函数前,由Ice:Application处理,并传给了Ice运行时。
Ice.ProgramName属性(argv[0])为应用程序名称。
可以通过通讯器的属性方法操作属性。
返回指定属性的字符串值,属性未定义,返回空串。
返回指定属性的字符串值,属性未定义,返回提供的缺省值。
类似getProperty,返回整数,若非有效数值,则返回0。
类似getPropertyAsInt,若非有效数值,则返回缺省值。
返回指定前缀的属性集合,类型为PropertyDict。
// ...
Ice::CommunicatorPtr ic;
// ...
ic = Ice::initialize(argc, argv);
//
Ice::PropertiesPtr props = ic->getProperties();
Ice::Int maxSize = props->getPropertyAsIntWithDefault("Filesystem.MaxFileSize",1024);
// ...
setProperty可将属性设置为指定值。由于通讯器在调用Ice:initialize(argc,argv);时就会读取配置文件,之后不再重新读配置。因此本函数应该在初始化之前调用,初始化之后设置属性,在重启应用程序前是不起作用的。
设置属性集:
Ice::PropertiesPtr props = Ice::createProperties(argc, argv);
props->setProperty("Ice.Trace.Network","0");
props->setProperty("Ice.Trace.Protocol","0");
// ...
Ice::InitializationData id;
id.properties = props;
Ice::CommunicatorPtr ic = Ice::initialize(id);
// ...
把属性集转换为命令行选项。
把命令行选项转换为属性集。
使用范例:
int main(int argc, char* argv[])
{
// 创建一个空属性集。
Ice::PropertiesPtr props = Ice::createProperties();
// 将argc/argv转换为字符串序列。
Ice::StringSeq args = Ice::argsToStringSeq(argc, argv);
// 析出所有以--Filesystem开始的选项。
args = props->parseCommandLineOptions("Filesystem", args);
// args现在只包含未析出的选项。所有以--Filesystem开头的选项被转换为属性,
// 存放在props对象中。现在,把剩余的参数转换回arg/argv向量。
Ice::stringSeqToArgs(args, argc, argv);
// 初始化通讯器。
Ice::InitializationData id;
id.properties = props;
Ice::CommunicatorPtr ic = Ice::initialize(argc, argv, id);
// 创建通讯器之后,argc/argv只包括与Ice属性和Filesystem属性无关的选项。
// ...
}
ICE采用的网络协议有TCP、UDP以及SSL三种,不同于WebService,ICE在调用模式上有好几种选择方案,并且每种方案正对不同的网络协议的特性做了相应的选择。
客户端只需将调用注册到本地传输缓冲区(Local Transport Buffers)后就立即返回,并不对调用结果负责。
最通用的模式,同步方法调用模式,只能用TCP或SSL协议。
类似于Oneway调用,不同的是 Datagram调用只能采用UDP协议而且只能调用无返回值和无输出参数的方法。
先将调用存在调用缓冲区里面,到达一定限额后自动批量发送所有请求(也可手动刷除缓冲区)。
与上类似。
不同的调用模式其实对应着不同的业务,对于大部分的有返回值的或需要实时响应的方法,我们可能都采用twoway方式调用,对于一些无需返回值或者不依赖返回值的业务,我们可以用Oneway或者BatchOneway方式,例如消息通知。剩下的Datagram和BatchDatagram方式,一般用在无返回值且不做可靠性检查的业务上,例如日志。
默认情况下,使用由ICE的请求调用模型也就是远程同步调用:一个函数操作调用行为像本地函数调用一样,就是客户端在调用期间线程被挂起,而在调用完成之后恢复。
#ifndef demo_idl
#define demo_idl
//
// version 1.0.0
//
module Demo
{
interface Hello
{
string sayHello(int num, out string strMsg);
};
};
#endif
利用Slice定义接口/类方法时和其他编程语言很相似,需要一个返回值和若干个参数,但是需要注意的是Slice不支持引用方式的参数传递,参数要么为输入参数,要么为输出参数,不同时为in和out。作为out参数的时候,不管客户端对out参数的初始赋值是什么,在服务端都取不到该值(参数strMsg在服务端始终为空),但是服务端可以对该参数进行赋值,再传递给客户端。还要注意的是:out参数一定是放在所有输入参数后面,不允许交叉使用。
#include
#include
using namespace std;
using namespace Demo;
class HelloI : public Demo::Hello
{
public:
virtual string sayHello(int nNum, string& strMsg, const Ice::Current&)
{
strMsg = "echo message.";
cout << "Hello World!" << endl;
return strMsg;
}
};
class HelloServer : public Ice::Application
{
public:
virtual int run(int, char*[]);
};
int
main(int argc, char* argv[])
{
HelloServer app;
return app.main(argc, argv);
}
int
HelloServer::run(int argc, char*[])
{
Ice::CommunicatorPtr ic = communicator();
Ice::ObjectAdapterPtr adapter = ic->createObjectAdapterWithEndpoints("HelloAdapter", "default -p 10000");
Demo::HelloPtr hello = new HelloI;
adapter->add(hello, communicator()->stringToIdentity("Hello"));
adapter->activate();
communicator()->waitForShutdown();
return EXIT_SUCCESS;
}
#include
#include
using namespace std;
using namespace Demo;
class HelloClient : public Ice::Application
{
public:
HelloClient();
virtual int run(int, char*[]);
private:
void menu();
};
int
main(int argc, char* argv[])
{
HelloClient app;
return app.main(argc, argv);
}
//
// Since this is an interactive demo we don't want any signal
// handling.
//
HelloClient::HelloClient() :
Ice::Application(Ice::NoSignalHandling)
{
}
int
HelloClient::run(int argc, char* argv[])
{
Ice::CommunicatorPtr ic = communicator();
Ice::ObjectPrx base = ic->stringToProxy("Hello:default -h 192.168.11.100 -p 10000");
HelloPrx hello = HelloPrx::checkedCast(base);
if (!hello)
{
cerr << argv[0] << ": invalid proxy" << endl;
return EXIT_FAILURE;
}
if (argc > 1)
{
cerr << appName() << ": too many arguments" << endl;
return EXIT_FAILURE;
}
menu();
char c;
do
{
try
{
int nNum = 1;
string strMsg = "Hello World!";
cout << "==> ";
cin >> c;
if (c == 't')
{
string strRet = hello->sayHello(nNum, strMsg);
cout << strRet << endl;
}
else if (c == 'x')
{
// Nothing to do
}
else if(c == '?')
{
menu();
}
else
{
cout << "unknown command `" << c << "'" << endl;
menu();
}
}
catch (const Ice::Exception& ex)
{
cerr << ex << endl;
}
}
while (cin.good() && c != 'x');
return EXIT_SUCCESS;
}
void
HelloClient::menu()
{
cout <<
"usage:\n"
"t: send greeting to server\n"
"x: exit\n"
"?: help\n";
}
ICE具备异步方法调用的API函数(AMI-- Asynchronous Method Invocation)。然而,这个API相当冗长而且使用不容易,应用不灵活。随着ICE3.4发布之后,ZeroC引入了一个新的API的异步方法调用不再遭受这些问题了,为开发人员提供了更多的选择,他们如何可以构建自己的代码。新特性的API适合可用于C++,Java,和Python。
ICE异步有两种形式,一种是客户端的AMI形式,一种是服务器端的AMD(Asynchronous Method Dispatch),在3.4版本之后AMI已经不再显示指定时,slice就会生成callback类,在客户端直接用say()是同步的,begin_say( …)就是异步的。
在实际的项目中,AMD操作一般将request data存入队列(比如,需要回调的对象和参数),让应用的线程(或线程池)稍后处理。这样,服务器就可以节省分配线程,所以可以高效同时支持上千个客户端。
另外一种AMD的用途是当客户请求结束后需要继续处理。为了使客户端产生最小的时延,当操作还再分配的时候就立即返回,然后用分派线程去干其他的事情。
AMI是客户端的功能,客户端调用完后马上返回,保证客户端不阻塞。AMD是服务器端功能,服务器端相应客户调用过程中。使用AMD可以在处理还没有完成时,就提前返回结果,就像在函数中间加个return语句一样,不同的是这个return下面的语句会接着执行。
还有一点很有特色的是,AMI和AMD是完全互相独立的,也就是说对于同一个interface,客户端不会知道服务器是否用AMD方式来回应请求,服务器端也不会知道客户端是否用AMI发起调用。而且,它们也无需知道,这些都是ICE内部实现的细节。
本文概述了最新的C++ API其最重要的特点。旧的API仍然可用,您可以在同一个程序中使用新的和旧的API。这会使您逐渐迁移旧代码,而不必马上改变您编写所有的旧API代码。我们强烈建议您使用新的API。(旧的API已被废弃,最终将被彻底删除了。)
#ifndef demo_ice
#define demo_ice
//
// version 1.0.0
//
module Demo
{
interface Hello
{
["ami" ] void say(string strMsg);
};
};
#endif // demo_ice
#include
#include
using namespace std;
using namespace Demo;
class HelloI : public Demo::Hello
{
public:
virtual void say(const string& strMsg, const Ice::Current&);
{
cout << strMsg << endl;
}
};
class HelloServer : public Ice::Application
{
public:
virtual int run(int, char*[]);
};
int
main(int argc, char* argv[])
{
HelloServer app;
return app.main(argc, argv);
}
int
HelloServer::run(int argc, char*[])
{
Ice::CommunicatorPtr ic = communicator();
Ice::ObjectAdapterPtr adapter = ic->createObjectAdapterWithEndpoints("HelloAdapter", "default -p 10000");
Demo::HelloPtr hello = new HelloI;
adapter->add(hello, communicator()->stringToIdentity("Hello"));
adapter->activate();
communicator()->waitForShutdown();
return EXIT_SUCCESS;
}
#include
#include
using namespace std;
using namespace Demo;
struct AMI_Hello : AMI_Hello_say
{
virtual void ice_response()
{
cout << "Server ice response" << endl;
}
virtual void ice_exception(const ::Ice::Exception& e)
{
cout << "Error:" << e << endl;
}
};
class HelloClient : public Ice::Application
{
public:
HelloClient();
virtual int run(int, char*[]);
private:
void menu();
};
int
main(int argc, char* argv[])
{
HelloClient app;
return app.main(argc, argv);
}
//
// Since this is an interactive demo we don't want any signal
// handling.
//
HelloClient::HelloClient() :
Ice::Application(Ice::NoSignalHandling)
{
}
int
HelloClient::run(int argc, char* argv[])
{
Ice::CommunicatorPtr ic = communicator();
Ice::ObjectPrx base = ic->stringToProxy("Hello:default -h 192.168.11.100 -p 10000");
HelloPrx hello = HelloPrx::checkedCast(base);
if (!hello)
{
cerr << argv[0] << ": invalid proxy" << endl;
return EXIT_FAILURE;
}
if (argc > 1)
{
cerr << appName() << ": too many arguments" << endl;
return EXIT_FAILURE;
}
menu();
char c;
do
{
try
{
string strMsg = "Hello World!";
cout << "==> ";
cin >> c;
if (c == 't')
{
hello->say(strMsg);
}
else if (c == 'a')
{
hello->say_async(new AMI_Hello, strMsg);
cout << "do other things..." << endl;
}
else if (c == 'n')
{
Ice::AsyncResultPtr asyncPtr = hello->begin_say(strMsg);
cout << "do other things..." << endl;
asyncPtr->waitForCompleted();
hello->end_say(asyncPtr);
}
else if (c == 'x')
{
// Nothing to do
}
else if(c == '?')
{
menu();
}
else
{
cout << "unknown command `" << c << "'" << endl;
menu();
}
}
catch (const Ice::Exception& ex)
{
cerr << ex << endl;
}
}
while (cin.good() && c != 'x');
return EXIT_SUCCESS;
}
void
HelloClient::menu()
{
cout <<
"usage:\n"
"t: send greeting to server\n"
"a: send greeting to server(Asynchronous Method Invocation)\n"
"n: send greeting to server(new AMI)\n"
"x: exit\n"
"?: help\n";
}