这一节大部分内容整理自ICE中文手册,在这里我特别感谢马维达同志的翻译给我们的学习带来了方便。
读服务端代码
文件server.cpp.
#include <Ice/Ice.h>
#include "../print.h"
using namespace std;
using namespace Demo;
//惯例,用后缀I 表示这个类实现一个接口
class PrinterI : public Printer {
public:
virtual void printString(const string& s, const Ice::Current&);
};
/*
打开print.h,看看PrinterI父类的定义
namespace Demo {
class Printer : virtual public Ice::Object {
public:
//纯虚函数,不能实例化
virtual void printString(const std::string&,
//第二个参数有缺省值,实现中可以不使用
const Ice::Current&= Ice::Current()) = 0;
};
};
*/
void PrinterI::printString(const string& s, const Ice::Current&)
{
cout << s << endl;
}
int main(int argc, char* argv[])
{
//程序的退出时的状态,就是否成功执行
int status = 0;
//来包含Ice run time 的主句柄 (main handle)
Ice::CommunicatorPtr ic;
try {
//初始化Ice run time (argc和argv是run time命令参数;
//就这个例子而言,服务器不需要任何命令行参数)。
//initialize 返回一个指向Ice::Communicator对象的智能指针,
//这个指针是Ice run time 的主句柄。
ic = Ice::initialize(argc, argv);
//调用Communicator 实例上的createObjectAdapterWithEndpoints,
//创建一个对象适配器(比如:网卡就是一种适配器)。
//参数是"SimplePrinterAdapter" (适配器的名字)
//和"default -p 10000"(用缺省协议(TCP/IP),侦听端口10000 的请求。)
//显然,在应用中硬编码对象标识和端口号,是一种糟糕的做法,
//但它目前很有效;我们将在以后看到在架构上更加合理的做法。
Ice::ObjectAdapterPtr adapter
= ic->createObjectAdapterWithEndpoints(
"SimplePrinterAdapter", "default -p 10000");
//服务器端run time 已经初始化,实例化一个PrinterI 对象,
//为我们的Printer 接口创建一个servant(serv 服务+-ant人,背一下单词)。
Ice::ObjectPtr object = new PrinterI;
//我们调用适配器的add,告诉它有了一个新的servant ;
//传给add 的参数是刚才实例化的servant,再加上一个标识符。
//在这里,"SimplePrinter" 串是servant 的名字
//(如果我们有多个打印机,每个打印机都可以有不同的名字,
//更正确的说法是,都有不同的对象标识)。
adapter->add(object,
Ice::stringToIdentity("SimplePrinter"));
//调用适配器的activate 方法激活适配器
//(适配器一开始是在暂停(holding)状态创建的;
//这种做法在下面这样的情况下很有用:
//我们有多个servant,它们共享同一个适配器,
//而在所有servant实例化之前我们不想处理请求)。
//一旦适配器被激活,服务器就会开始处理来自客户的请求。
adapter->activate();
//最后,我们调用waitForShutdown。
//这个方法挂起发出调用的线程直到服务器实现终止
//——或者是通过发出一个调用关闭run time,
ic->waitForShutdown();
}
catch (const Ice::Exception& e) {
cerr << e << endl;
status = 1;
} catch (const char* msg) {
cerr << msg << endl;
status = 1;
}
if (ic) {
try {
//必须调用Communicator::destroy结束Ice run time。
//destroy 会等待任何还在运行的操作调用完成。
//此外, destroy 还会确保任何还未完成的线程都得以汇合(joined),
//并收回一些操作系统资源,比如文件描述符和内存。
//决不要让你的main 函数不调用destroy 就终止,
//否则,后果无法想象。
ic->destroy();
} catch (const Ice::Exception& e) {
cerr << e << endl;
status = 1;
}
}
return status;
}
注意,尽管以上的代码不算少,但它们对所有的服务器都是一样的。你可以把这些代码放在一个辅助类里,然后就无需再为它费心了(Ice 提供了这样的辅助类,叫作Ice::Application,参见 10.3.1 节) 。就实际的应用代码而言,服务器只有几行代码:六行代码定义PrinterI 类,再加上三2 行代码实例化一个PrinterI 对象,并向对象适配器注册它。
读客户端代码
文件client.cpp.
#include <Ice/Ice.h>
#include "..\print.h"
using namespace std;
using namespace Demo;
int main(int argc, char* argv[])
{
int status = 0;
Ice::CommunicatorPtr ic;
try {
ic = Ice::initialize(argc, argv);
//stringToProxy 返回的代理(Proxy)类型是Ice::ObjectPrx,
//这种类型位于接口和类的继承树的根部(接口的基类)。
Ice::ObjectPrx base
=ic->stringToProxy( "SimplePrinter:default -p 10000");
//但要实际要与我们的打印机交谈,
//我们需要的是Printer 接口、不是Object 接口的代理。
//为此,需要调用PrinterPrx::checkedCast 进行向下转换(向下转型)。
//这个方法会发送一条消息给服务器,
//询问“这是Printer 接口的代理吗?”
//如果回答“是”,就会返回Printer 的一个代理;
//如果代理代表的是其他类型的接口,返回一个空代理
PrinterPrx printer = PrinterPrx::checkedCast(base);
//测试向下转型是否成功,若不成功,就抛出出错消息并终止客户。
if (!printer) throw "Invalid proxy";
//现在,我们在我们的地址空间里有了一个激活的代理,
//可以调用printString 方法,
//把享誉已久的 "Hello World!" 串传给它。
//服务器会在它的终端上打印这个串。
printer->printString("Hello World!");
}
catch (const Ice::Exception& ex) {
cerr << ex << endl;
status = 1;
} catch (const char* msg) {
cerr << msg << endl;
status = 1;
}
if (ic)
ic->destroy();
return status;
}
如果出现任何错误,客户会打印一条出错消息。例如,如果我们没有先启动服务器就运行客户,我们会得到:
Network.cpp:471: Ice::ConnectFailedException:
connect failed: Connection refused
(由于windows下的命令行窗口在出错后会一闪就消失,不过我们可以在client.cpp的main函数的return status;之前加上system("PAUSE");然后再在VS2003.net中把client设置为启动项目,重新编译,运行。OK,可以看到结果了。)