ICE入门

部分参考自:https://www.cnblogs.com/SGSoft/archive/2007/05/02/734454.html

                     https://blog.csdn.net/liuxuezong/article/details/28925453 

                    https://blog.csdn.net/qingen1/article/details/13052139

1 前言
        软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用(oneway);回调是一种双向调用模式(twoway),也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。

2 常见知识
Client(客户端)
       估计这个大家都非常清楚,通俗的讲就是一个请求的发起方。

Proxy(代理)
       代理实际上就是远程服务驻在本地的一个代表,创建它时首先会和远程服务经行握手和状态确认等操作,Client所有的操作都是通过Proxy来办理的。代理又分为直接代理(已经知道服务端的位置及其他信息)和间接代理(不知道服务器在哪里,由Registry注册器告诉它地址等信息)。

Adapter(适配器)
        Adapter是配置相当于服务单位(Servant)的管理者,它管理着每个请求该分配给哪一个服务单位。

Servant(服务单元)
       它是服务的最小单位,一般是具体到 某个接口的实现

Interface(接口)
        ICE我们会经常提到了接口的实现,但是这个接口是谁定义的,这个时候我们不免要提到Slice(Specification Language for Ice),也就是ICE所使用的“语言”,正是有了这个“中间语言”,我们才可以做到支持各种编程语言,因为您所使用的语言只要跟Slice打交道就可以了。
 

1.先去ICE官网https://zeroc.com/下载安装包,我下载的是3.5版本

将安装目录 "..\Ice-3.5.1\bin" (目录下有slice2cpp.exe)加入环境变量"Path"中

环境变量设置好后在控制台下输入命令:slice2cpp -v 

得到的结果是: 3.5.1  ,证明前面安装及设置环境变量成功。

2.写一个自己的Slice接口定义

编写ICE应用程序的第一步是定义slice文件。为了支持客户端和服务端使用不同的编程语言来实现,ICE定义了一种独立于各种开发语言,用来描述服务端提供的接口的语言,即slice。客户端根据slice文件可以知道服务端有哪些服务可以提供,并且知道应该以什么样的方式来使用服务端提供的服务。服务端则在slice文件定义的接口基础上去实现接口的功能。
新建一个文件"MyPrint.ice",内容如下:

module Demo
{
    interface MyPrinter
    {
        void printString(string s);
    };
};

在这段代码中,定义了一个模块Demo。在此模块中有一个接口MyPrinter。本示例程序只需要服务端提供一个最简单的输出功能,因此这个接口目前也非常简单,只提供一个方法printString。该方法接收一个字符串类型的参数s。

顺说一句:官网示例程序后面2个分号都掉了,不严谨啊,囧

在控制台下输入命令:slice2cpp MyPrint.ice,此为编译ICE文件,转换为c++ 的文件。

此时在"MyPrint.ice"同级目录下会自动生成"MyPrint.h"和"MyPrint.cpp"

注意接口名"MyPrinter"

slice转换为c++代码的过程是比较直观的。module将被转换为c++中的namespace,而interface将被转换为一个抽象类,interface中的方法也会被转换为该抽象类中的方法。服务端的代码从该抽象类派生一个具体类,并实现抽象类中的方法,这样就可以提供RPC接口的功能。
 

3.在VS中建一个解决方案"IceTest",并添加两个工程:"Server"、 "Client"

将刚生成的"MyPrint.h"和"MyPrint.cpp"放在"IceTest"目录下

项目目录结构如下:

 服务端代码如下:

#include <Ice/Ice.h>

#include "../MyPrint.h"

using namespace std;
using namespace Demo;

//惯例,用后缀I 表示这个类实现一个接口
class PrinterI : public MyPrinter
{
public:
	virtual void printString(const string& s, const Ice::Current&);
};

/*
打开MyPrint.h,看看PrinterI父类MyPrinter的定义
namespace Demo 
{
	class MyPrinter : virtual public Ice::Object
	{
		public:
			//纯虚函数,不能实例化. 第二个参数有缺省值,实现中可以不使用
			virtual void printString(const std::string&,
                                     const Ice::Current&= Ice::Current()) = 0);
	}
};
*/

//PrinterI继承自定义的接口MyPrinter
//*********关键在此,在这里实现方法printString***********
void PrinterI::printString(const string& s, const Ice::Current&)
{
	cout << s << endl;
}

int main(int argc, char* argv[])
{
    //Ice run time 的主句柄
	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 9999"(用缺省协议(TCP/IP),侦听端口9999 的请求。)
		//显然,在应用中硬编码对象标识和端口号,是一种糟糕的做法,
		//但它目前很有效;我们将在以后看到在架构上更加合理的做法。
		Ice::ObjectAdapterPtr adapter =
			ic->createObjectAdapterWithEndpoints("SimplePrinterAdapter",
                                                 "default -p 9999");

                //服务器端run time 已经初始化,实例化一个PrinterI 对象,
		//为我们的Printer 接口创建一个servant
                //这里和我们定义的接口关联(PrinterI继承自接口MyPrinter)
		Ice::ObjectPtr object = new PrinterI;

                //我们调用适配器的add,告诉它有了一个新的servant ;
		//传给add 的参数是刚才实例化的servant,再加上一个标识符。
		//在这里,"SimplePrinter" 串是servant 的名字
		//(如果我们有多个打印机,每个打印机都可以有不同的名字,
		//更正确的说法是,都有不同的对象标识)。
		adapter->add(object, ic->stringToIdentity("SimplePrinter"));
		cout<<"server start..."<<endl;

                //调用适配器的activate 方法激活适配器
		//(适配器一开始是在暂停(holding)状态创建的;
		//这种做法在下面这样的情况下很有用:
		//我们有多个servant,它们共享同一个适配器,
		//而在所有servant实例化之前我们不想处理请求)。
		//一旦适配器被激活,服务器就会开始处理来自客户的请求。
		adapter->activate();

                //最后,我们调用waitForShutdown。
		//这个方法挂起发出调用的线程直到服务器实现终止
		//——或者是通过发出一个调用关闭run time,
		ic->waitForShutdown();
	} 
	catch(const std::exception& e)
	{
		cerr << e.what() << endl;
		return 1;
	}


	return 0;
}

客户端代码如下:

#include <Ice/Ice.h>

#include "../MyPrint.h"

using namespace std;
using namespace Demo;

int main(int argc, char* argv[])
{
	Ice::CommunicatorPtr ic;
	try
	{
		ic=Ice::initialize(argc,argv);

                //stringToProxy 返回的代理(Proxy)类型是Ice::ObjectPrx,
		//这种类型位于接口和类的继承树的根部(接口的基类)。
		Ice::ObjectPrx base = ic->stringToProxy("SimplePrinter:default -p 9999");

                //但要实际要与我们的打印机交谈,
		//我们需要的是MyPrinter 接口、不是Object 接口的代理。
		//为此,需要调用MyPrinterPrx::checkedCast 进行向下转换(向下转型)。
		//这个方法会发送一条消息给服务器,
		//询问“这是MyPrinter 接口的代理吗?”
		//如果回答“是”,就会返回MyPrinter 的一个代理;
		//如果代理代表的是其他类型的接口,返回一个空代理
                //注意代理类名:接口后面+"Prx"
		MyPrinterPrx printer = MyPrinterPrx::checkedCast(base);

                //测试向下转型是否成功,若不成功,就抛出出错消息并终止客户。
		if(!printer)
		{
			throw "Invalid proxy";
		}

                //现在,我们在我们的地址空间里有了一个激活的代理,
		//可以调用printString 方法,
		//把享誉已久的 "Hello World!" 串传给它。
		//服务器会在它的终端上打印这个串。
		printer->printString("Hello World!");
	} 
	catch(const std::exception& ex)
	{
		cerr << ex.what() << endl;
		return 1;
	}
	return 0;
}

项目属性中:

配置附加包含目录(ICE头文件目录),如“...\Ice-3.5.1\include”

配置附加库目录(ICE库目录),如“...\Ice-3.5.1\lib”

配置附加依赖项(ICE库),将"ice.lib"和"iceutil.lib"添加进去,注意Release/Debug版本库区别

4.运行测试

先运行Server,再运行Client

每运行一次Client时,Server都会输出一次“Hello world”,如图:

tips:在仿照官网示例程序时,端口号选择"10000",然后运行程序不正常。

发现在客户端程序"checkedCast()"处失败,经定位后发现是"10000"端口在本机被占用。

猜你喜欢

转载自blog.csdn.net/andylanzhiyong/article/details/90143343
ice