本文主要介绍ZeroC Ice的相关知识,同时通过一个Ice的demo程序介绍其使用方法。
1 ZeroC Ice介绍
Ice(Internet Communications Engine)是一个面向对象的RPC(Remote Procedure Call,即远程过程调用)框架,它可以帮助你轻松地构建分布式应用。通过使用Ice,用户可以将更多的注意力放在自己应用程序的逻辑上,至于底层网络程序接口的交互则由Ice负责。通过使用Ice,用户不需要再为一些细节担忧,例如开放式网络连接、网路传输的序列化和反序列化,以及连接的失败重传问题。
ZeroC Ice的客户端和服务端的架构图如下:
2 demo构建
说明:本文介绍的demo程序是在Centos 7上、使用C++编程语言开发的,对应的Ice版本为“3.6.4”。
2.1 安装Ice
通过下列步骤安装Ice。
1. 添加Ice的yum仓库:
cd /etc/yum.repos.d
wget https://zeroc.com/download/Ice/3.6/el7/zeroc-ice3.6.repo
2. 安装Ice:
yum install ice-all-runtime ice-all-devel
2.2 编写Slice定义文件
开发Ice程序的第一步就是编写Slice定义文件,该文件包含了Ice应用程序需要用到的接口。
在本文中,我们编写名为“Hello.ice”的Slice定义文件。“Hello.ice”内容如下:
module Demo {
interface Hello {
string SayHello(string username);
};
};
上面的Slice定义文件内,包括了Demo模块,Demo模块中又包含了Hello接口。当前,Hello接口仅提供了一个操作:SayHello。SayHello操作的具体功能需要我们后续去实现。
2.3 编译Slice定义文件
编译前面编写的Slice定义文件,生成编译C++所需要的文件,命令如下:
slice2cpp Hello.ice
根据“Hello.ice”,上面的命令会生成两个文件:“Hello.h”和“Hello.cpp”。其中:
- Hello.h:“Hello.h”头文件包含了Slice定义文件中定义的Hello接口(当前,现在这个接口是以C++类型定义的),在客户端与服务端的源码中都要包含“Hello.h”;
- Hello.cpp:“Hello.cpp”包含了Hello接口的实现源码。这些源码包括了针对客户端和服务端的(参数/返回值)类型指定及运行支持。例如,它包含了客户端的参数数据序列化,以及服务端数据反序列化。在客户端和服务端进行代码编译和连接的过程中,都需要包含“Hello.cpp”。
说明:从架构角度看,由“Hello.ice”生成的“Hello.h”和“Hello.cpp”,相当于C++的proxies和skeletons。
2.4 编写接口处理类声明及实现代码
创建接口处理类HelloI,HelloI继承了生成类Hello(由“*.ice”文件生成)。在HelloI中定义了(“*.ice”文件中)SayHello操作的具体实现。
类HelloI声明代码(HelloI.h)如下:
#ifndef __HELLOI_H__
#define __HELLOI_H__
#include <Hello.h>
using namespace std;
class HelloI : public Demo::Hello
{
public:
virtual string SayHello(const string&, const Ice::Current&);
};
#endif
类HelloI的实现代码(HelloI.cpp)如下:
#include <Ice/Ice.h>
#include <HelloI.h>
using namespace std;
string HelloI::SayHello(const string& s, const Ice::Current&)
{
string wholestr;
cout << "server receive msg: " << s << endl;
wholestr = "hello " + s;
cout << "server return msg: " << wholestr << endl;
return wholestr;
}
注意:本文到现在为止,介绍的几个文件(Hello.ice、Hello.h、Hello.cpp、HelloI.h、HelloI.cpp)为本ZeroC Ice系列博客的demo搭建时使用的基础框架文件,后面对于ZeroC各个应用模式的demo搭建,如无特殊说明,均沿用了这五个基础文件。现给出ZeroC各应用模式的demo链接:
2.5 编写服务端代码
服务端代码创建接口处理类HelloI的实例,并将该实例关联到指定的Ice对象ID(本例中Ice对象ID为“hello”),客户端通过这个Ice对象ID调用服务端(接口处理类中定义的)方法。
服务端代码(server.cpp)如下:
#include <Ice/Ice.h>
#include <HelloI.h>
using namespace std;
int main(int argc, char* argv[])
{
int status = 0;
Ice::CommunicatorPtr ic;
try
{
ic = Ice::initialize(argc, argv);
Ice::ObjectAdapterPtr adapter = ic->createObjectAdapterWithEndpoints("HelloAdapter", "default -p 10000");
Ice::ObjectPtr object = new HelloI;
adapter->add(object, ic->stringToIdentity("hello"));
adapter->activate();
cout << "server started, waiting for client connect..." << endl;
ic->waitForShutdown();
}
catch (const Ice::Exception& e)
{
cerr << e << endl;
status = 1;
}
catch (const char* msg)
{
cerr << msg << endl;
status = 1;
}
if (ic)
{
try
{
ic->destroy();
}
catch (const Ice::Exception& e)
{
cerr << e << endl;
status = 1;
}
}
return status;
}
2.6 编写客户端代码
客户端代码通过服务端提供的Ice对象ID连接服务端,并调用(与该Ice对象ID)相关的方法。
客户端代码(client.cpp)如下:
#include <Ice/Ice.h>
#include <Hello.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);
Ice::ObjectPrx base = ic->stringToProxy("hello:default -p 10000");
HelloPrx hello = HelloPrx::checkedCast(base);
if (!hello)
{
throw "Invalid proxy";
}
string result = "";
result = hello->SayHello("liitdar");
cout << "client's result: " << result << endl;
}
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;
}
2.7 编译生成客户端和服务端的应用程序
编译生成服务端应用程序(server),命令如下:
g++ -o server -I. server.cpp Hello.cpp HelloI.cpp -lIce -lIceUtil -lpthread
编译生成客户端应用程序(client),命令如下:
g++ -o client -I. client.cpp Hello.cpp -lIce -lIceUtil -lpthread
2.8 运行服务端与客户端程序
在一个终端运行服务端程序,如下:
./server
新打开一个终端,运行客户端程序,如下:
./client
正常情况下,我们能够在上面的两个终端中看到服务端与客户端的信息交互情况,如下:
【服务端】:
【客户端】:
如果两个终端中出现了上述信息,说明demo程序部署成功了。
上述信息交互过程为:
- 服务端启动,等待客户端连接;
- 客户端连接服务端,发送“liitdar”到服务端,调用服务端的SayHello方法(“liitdar”作为SayHello方法的参数);
- 服务端执行SayHello方法,并在终端上打印相关信息;
- 服务端将SayHello方法的返回值返回给客户端;
- 客户端收到服务端的返回值,打印该返回值。