本文主要介绍ZeroC Ice的相关知识,以及如何构建一个Ice的demo程序。
1. ZeroC Ice介绍
Ice(Internet Communications Engine)是一个面向对象的RPC框架,它可以帮助你轻松地构建分布式应用。通过使用Ice,用户可以将更多的注意力放在自己应用程序的逻辑上,至于底层网络程序接口的交互则由Ice负责。通过使用Ice,用户不需要再为一些细节担忧,例如开放式网络连接、网路传输的序列化和反序列化,以及连接的失败重传问题。
ZeroC Ice的客户端和服务端的架构图如下:
2. demo构建
说明:本文介绍的demo程序是在Centos 7上、使用C++语言开发的,对应的Ice版本为3.6.4。
2.1 安装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程序部署成功了。
上述信息交互过程为:
a)服务端启动,等待客户端连接;
b)客户端连接服务端,发送“liitdar”到服务端,调用服务端的SayHello方法(“liitdar”作为SayHello方法的参数);
c)服务端执行SayHello方法,并在终端上打印相关信息;
d)服务端将SayHello方法的返回值返回给客户端;
e)客户端收到服务端的返回值,打印该返回值。