ICE的优势是作为通讯中间件可支持跨平台的通讯,目前支持C++、C#、JAVA、Python、Ruby等多种语言。
一 、示例描述
搭建一个简单的聊天室,服务端使用C++实现,客户端也使用C++实现,中间以ICE构建通讯通道。聊天室实现的功能包括:用户登录/注销、发送消息、接受消息。
二 、准备Slice文件
Slice作为ICE本身特有的语言,成为跨平台通讯的桥梁。通过Slice文件描述需要实现的接口,再由ICE提供的各个语言编译器生成各语言的接口类,各语言分别负责根据接口类实现属于本身的逻辑细节即可,据说这种思想是从老牌的通讯中间件CORBA中借鉴过来的。
聊天室的Slice文件定义如下:
module ChatRoom{
interface CAbsMsgReceiver{//lient端实现,Server端调用的回调函数接口
//这是因为Server端在向Client端发送信息的时候,必须知道Client端的通讯地址
void OnMsg(string sender, string msg);
};
interface CAbsConnAcceptor{//Server端实现,Client端调用。实现了登录、注销和发送信息的接口函数
bool Login(string user, CAbsMsgReceiver* receiver);
void Logout(string user);
void Send(string user, string msg);
};
//Server端用来存储Client端的用户名和回调函数句柄的容器
dictionary<string, CAbsMsgReceiver*> dicClient;
};
分析上面的Slice文件如下:
1、 在命名空间为ChatRoom下定义了两个类(接口),CAbsConnAcceptor和CAbsMsgReceiver。
2、 CAbsConnAcceptor主要是Server端实现,Client端调用。实现了登录、注销和发送信息的接口函数。
3、 CAbsMsgReceiver主要是Client端实现,Server端调用的回调函数接口。这是因为Server端在向Client端发送信息的时候,必须知道Client端的通讯地址。因此需要这个回调函数类。
4、 dicClient主要是Server端用来存储Client端的用户名及回调函数句柄的。
三 、实现Server端
通过Slice2Cpp程序将Slice文件生成Message.h和Message.cpp,将两个文件加入到工程中,同时新建一个类CConnAcceptor继承CAbsCoonAcceptor,实现CAbsCoonAcceptor的三个虚拟函数接口,这个类被称为Server端的Servant类。代码如下:
#include <Ice/Ice.h>
#include "Message.h"
using namespace ChatRoom;
#include <string>
#include <iostream>
#include <iomanip>
using namespace std;
class CConnAcceptor : public CAbsConnAcceptor{
public:
CConnAcceptor();
~CConnAcceptor();
virtual bool Login(const ::std::string&, const ::ChatRoom::CAbsMsgReceiverPrx&, const ::Ice::Current&);
virtual void Logout(const ::std::string&, const ::Ice::Current&);
virtual void Send(const ::std::string&, const ::std::string&, const ::Ice::Current&);
private:
ChatRoom::dicClient mapUser;
void BoardCast(const ::std::string user, const ::std::string msg);
bool Notify(const ::std::string& user, const ::std::string& msg, const ::ChatRoom::CAbsMsgReceiverPrx& receiver);
};
CConnAcceptor::CConnAcceptor(){}
CConnAcceptor::~CConnAcceptor(){}
bool CConnAcceptor::Login(const ::std::string& user, const ::ChatRoom::CAbsMsgReceiverPrx& receiver, const ::Ice::Current&){
if(mapUser.find(user) != mapUser.end()) return false;//防止同一用户的重复登录
cout << user << "登录成功!" << endl;
mapUser.insert(ChatRoom::dicClient::value_type(user, receiver));
BoardCast(user, "欢迎进入聊天室,希望大家踊跃发言!");
return true;
}
void CConnAcceptor::Logout(const ::std::string& user, const ::Ice::Current& = ::Ice::Current()){
cout << user << "注销成功!" << endl;
mapUser.erase(user);
}
void CConnAcceptor::Send(const ::std::string& user, const ::std::string& msg, const ::Ice::Current&){
BoardCast(user, msg);
}
void CConnAcceptor::BoardCast(const ::std::string user, const ::std::string msg){
for(ChatRoom::dicClient::iterator it = mapUser.begin(); it != mapUser.end(); it++){
Notify(it->first, msg, it->second);
}
}
bool CConnAcceptor::Notify(const ::std::string& user, const ::std::string& msg, const ::ChatRoom::CAbsMsgReceiverPrx& receiver){
bool bNormal = true;
try{ receiver->OnMsg(user, msg); }
catch(::std::exception& e){ bNormal = false; }
return bNormal;
}
class CMyApp : virtual public Ice::Application{
public:
virtual int run(int, char* []){
shutdownOnInterrupt();// Terminate cleanly on receipt of a signal
Ice::ObjectAdapterPtr spObjAdapter = communicator()->createObjectAdapterWithEndpoints("ServerAdapter", "default -h 127.0.0.1 -p 5432");//Create an object adapter
ChatRoom::CAbsConnAcceptorPtr spAcceptor = new CConnAcceptor();
spObjAdapter->add(spAcceptor, communicator()->stringToIdentity("Acceptor"));
spObjAdapter->activate();
communicator()->waitForShutdown();
if(interrupted()) cerr << appName() << ": received signal, shutting down" << endl;
return 0;
}
};
int main(int argc, char* argv[])
{
CMyApp oMyApp;
int iRet = oMyApp.main(argc, argv);
system("Pause");
return iRet;
}
注意有两个私有函数BoardCast和Notify为从Server端广播消息至Client端时用到的。
四 、实现Client端
通过Slice2Cpp程序将Slice文件生成Message.h和Message.cpp,将两个文件加入到工程中,同时新建一个类CMsgReceiver继承CAbsCMsgReceiver,实现CAbsCMsgReceiver的OnMsg函数。新建Client类,实现Client端与Server端之间的通讯逻辑,并生成CAbsCMsgReceiver类的本地代理类,发送到Server端实现发送消息的回调逻辑。代码如下:
#include <Ice/Ice.h>
#include "Message.h"
using namespace ChatRoom;
#include <string>
#include <iostream>
#include <iomanip>
using namespace std;
class CMsgReceiver : public CAbsMsgReceiver{
virtual void OnMsg(const ::std::string& sender, const ::std::string& msg, const ::Ice::Current&);
};
void CMsgReceiver::OnMsg(const ::std::string& sender, const ::std::string& msg, const ::Ice::Current&){
cout << sender << "˵ : \" " << msg << "\"" << endl;
}
class CMyApp : virtual public Ice::Application{
public:
virtual int run(int, char* []){
shutdownOnInterrupt();// Terminate cleanly on receipt of a signal
Ice::ObjectPrx pAcceptorProxy = communicator()->stringToProxy("Acceptor:default -h 127.0.0.1 -p 5432");
CAbsConnAcceptorPrx spAcceptor = CAbsConnAcceptorPrx::checkedCast(pAcceptorProxy);
if(!spAcceptor) throw "Bad AcceptorProxy !";
Ice::ObjectAdapterPtr spObjAdapter = communicator()->createObjectAdapterWithEndpoints("ClientAdapter", "default -h 127.0.0.1 -p 2345");//Create an object adapter
ChatRoom::CAbsMsgReceiverPtr spUser = new CMsgReceiver();
spObjAdapter->add(spUser, communicator()->stringToIdentity("Receiver"));
Ice::ObjectPrx pReceiverProxy = communicator()->stringToProxy("Receiver:default -h 127.0.0.1 -p 2345");
CAbsMsgReceiverPrx spReceiver = CAbsMsgReceiverPrx::checkedCast(pReceiverProxy);
if(!spReceiver) throw "Bad ReceiverProxy !";
spAcceptor->Login("ZhangSan", spReceiver);
spAcceptor->Send("ZhangSan", "Hello, Everyone");
spAcceptor->Logout("ZhangSan");
}
};
int main(int argc, char* argv[])
{
CMyApp oMyApp;
int iRet = oMyApp.main(argc, argv);
system("Pause");
return iRet;
}