服务器实现: 服务器使用C++。注意它的结构:类 ChatRoom 实现了大部分的应用逻辑。为了支持推模型与拉模型,服务器实现了类ChatSession 和类 PollingChatSession。 ChatRoom 调用 ChatRoomCallbackAdapter 对象的 send 函数来传递客户消息,该对象隐藏了两种模型之间的差异。
ChatRoom 实现:
ChatRoom是一个普通的C++对象,而不是一个Servant.
// C++
class ChatRoomCallbackAdapter { /* */ };
typedef IceUtil::Handle<ChatRoomCallbackAdapter> ChatRoomCallbackAdapterPtr;
class ChatRoom : public IceUtil::Shared
{
public:
void reserve(const string&);
void unreserve(const string&);
void join(const string&, const ChatRoomCallbackAdapterPtr&);
void leave(const string&);
Ice::Long send(const string&, const string&);
private:
typedef map<string, ChatRoomCallbackAdapterPtr> ChatRoomCallbackMap;
ChatRoomCallbackMap _members;
set<string> _reserved;
IceUtil::Mutex _mutex;
};
typedef IceUtil::Handle<ChatRoom> ChatRoomPtr;
成员_reserverd是一个字符串集合,它存储已经建立回话,但是还没有加入聊天室的客户名。_members存储当前聊天室的所有用户(已经调用过join函数的用户)。
成员函数 reserve 和 unreserve 维护 _reserved 集合。
// C++
void
ChatRoom::reserve(const string& name)
{
IceUtil::Mutex::Lock sync(_mutex);
if(_reserved.find(name) != _reserved.end() || _members.find(name) != _members.end())
{
throw string("The name " + name + " is already in use.");
}
_reserved.insert(name);
}
void
ChatRoom::unreserve(const string& name)
{
IceUtil::Mutex::Lock sync(_mutex);
_reserved.erase(name);
}
join操作添加用户到聊天室。
// C++
void
ChatRoom::join(const string& name, const ChatRoomCallbackAdapterPtr& callback)
{
IceUtil::Mutex::Lock sync(_mutex);
IceUtil::Int64 timestamp = IceUtil::Time::now().toMilliSeconds();
_reserved.erase(name);
Ice::StringSeq names;
ChatRoomCallbackMap::const_iterator q;
for(q = _members.begin(); q != _members.end(); ++q)
{
names.push_back((*q).first);
}
callback->init(names);
_members[name] = callback;
UserJoinedEventPtr e = new UserJoinedEvent(timestamp, name);
for(q = _members.begin(); q != _members.end(); ++q)
{
q->second->join(e);
}
}
send实现,同join实现非常类似:
// C++
Ice::Long
ChatRoom::send(const string& name, const string& message)
{
IceUtil::Mutex::Lock sync(_mutex);
IceUtil::Int64 timestamp = IceUtil::Time::now().toMilliSeconds();
MessageEventPtr e = new MessageEvent(timestamp, name, message);
for(ChatRoomCallbackMap::iterator q = _members.begin(); q != _members.end(); ++q)
{
q->second->send(e);
}
return timestamp;
}
类 ChatRoomCallbackAdapter
// C++
class ChatRoomCallbackAdapter : public IceUtil::Shared
{
public:
virtual void init(const Ice::StringSeq&) = 0;
virtual void join(const UserJoinedEventPtr&) = 0;
virtual void leave(const UserLeftEventPtr&) = 0;
virtual void send(const MessageEventPtr&) = 0;
};
推模式 CallbackAdapter 实现:
class SessionCallbackAdapter : public ChatRoomCallbackAdapter
{
public:
SessionCallbackAdapter(const ChatRoomCallbackPrx& callback, const ChatSessionPrx& session) : _callback(callback), _session(session)
{
}
void init(const Ice::StringSeq& users)
{
_callback->init_async(new AMICallback<AMI_ChatRoomCallback_init>(_session), users);
}
void join(const UserJoinedEventPtr& e)
{
_callback->join_async(new AMICallback<AMI_ChatRoomCallback_join>(_session),
e->timestamp,
e->name);
}
void leave(const UserLeftEventPtr& e)
{
_callback->leave_async(new AMICallback<AMI_ChatRoomCallback_leave>(_session),
e->timestamp,
e->name);
}
void send(const MessageEventPtr& e)
{
_callback->send_async(new AMICallback<AMI_ChatRoomCallback_send>(_session),
e->timestamp,
e->name,
e->message);
}
private:
const ChatRoomCallbackPrx _callback;
const ChatSessionPrx _session;
};
看一下SessionCallbackAdapter的四个成员函数,当异步调用完成时,都使用类AMICallback来接收通知。它的定义如下:
template<class T> class AMICallback : public T
{
public:
AMICallback(const ChatSessionPrx& session) : _session(session)
{
}
virtual void ice_response()
{
}
virtual void ice_exception(const Ice::Exception&)
{
try
{
_session->destroy(); // Collocated
}
catch(const Ice::LocalException&)
{
}
}
private:
const ChatSessionPrx _session;
};
当用户回调操作抛出异常,服务器立即销毁客户会话,即把该用户赶出聊天室。这是因为,一旦客户的回调对象出现了一次异常,它以后也就不可能再正常。
推模式会话创建:
现在来看一下会话创建。推模式的客户使用Glacier2,所以要使用Glacier2的会话创建机制。Glacier2 允许用户通过提供一个Glacier2::SessionManager对象的代理来自定义会话创建机制。通过设置Glacier2.SessionManager属性来配置Gloacier2,就可以使用自己的会话管理器。会话管理器除了一个trivial构造函数(设置聊天室指针),只有一个操作,create,Glacier2调用它来代理应用的会话创建。 create 操作必须返回一个会话代理(类型为Glacier2::Session*)。实现如下:
Glacier2::SessionPrx
ChatSessionManagerI::create(const string& name,
const Glacier2::SessionControlPrx&,
const Ice::Current& c)
{
string vname;
try
{
vname = validateName(name);
_chatRoom->reserve(vname);
}
catch(const string& reason)
{
throw CannotCreateSessionException(reason);
}
Glacier2::SessionPrx proxy;
try
{
ChatSessionIPtr session = new ChatSessionI(_chatRoom, vname);
proxy = SessionPrx::uncheckedCast(c.adapter->addWithUUID(session));
Ice::IdentitySeq ids;
ids.push_back(proxy->ice_getIdentity());
sessionControl->identities()->add(ids);
}
catch(const Ice::LocalException&)
{
if(proxy)
{
proxy->destroy();
}
throw CannotCreateSessionException("Internal server error");
}
return proxy;
}
首先调用一个简单的帮助函数 validateName, 来检查传递的用户名是否包含非法字符,并把它转为大写,然后调用 reserver函数把它加到聊天室的_reserved集合中。我们要监视这些操作抛出的消息,并把它转化为Glacide2::CannotCreateSessionException异常,即在create操作的异常规范声明的异常。
接着实例化一个ChatSessionI对象(见下面)来创建会话。注意这个会话使用UUID作为对象标识,所以保证标识符唯一。
最后,添加这个新创建的会话标识,Gllacier2只通过它来转发经过这个会话的请求。实际上,“只转发经过这个会话的并且只到这个会话的请求”,这是一种安全的办法:如果有恶意客户能猜出另一个客户会话的标识,它也不能向别的对象发送请求(可能在除了聊天服务器之外的服务器上)。如果出错,就销毁刚创建的会话对象,这样避免了资源泄露。
这就是利用Glacier2创建会话的全部。如果你希望使用Glacier2的认证机制,可以设置属性Glacier2.PermissionsVerifier为执行认证的对象代理。(Glacier2提供一个内置的权限验证器,NullPermissionsVerifier,可以检查用户名和密码)。
图:会话创建交互图(略)
ChatSessionI类实现了ChatSession接口。
class ChatSessionI : public ChatSession
{
public:
ChatSessionI(const ChatRoomPtr&, const string&);
virtual void setCallback(const ChatRoomCallbackPrx&, const Ice::Current&);
virtual Ice::Long send(const string&, const Ice::Current&);
virtual void destroy(const Ice::Current&);
private:
const ChatRoomPtr _chatRoom;
const string _name;
ChatRoomCallbackAdapterPtr _callback;
bool _destroy;
IceUtil::Mutex _mutex;
};
typedef IceUtil::Handle<ChatSessionI> ChatSessionIPtr;
构造函数设置聊天室和用户名,并把_destroy设置为False.
由于Glacier2::create操作不允许传递代理,必须把创建会话和设置回调分成两步。这是setCallback的实现;
void
ChatSessionI::setCallback(const ChatRoomCallbackPrx& callback, const Ice::Current& c)
{
IceUtil::Mutex::Lock sync(_mutex);
if(_destroy)
{
throw Ice::ObjectNotExistException(__FILE__, __LINE__);
}
if(_callback || !callback)
{
return;
}
Ice::Context ctx;
ctx["_fwd"] = "o";
_callback = new SessionCallbackAdapter(callback->ice_context(ctx),
ChatSessionPrx::uncheckedCast(
c.adapter->createProxy(c.id)));
_chatRoom->join(_name, _callback);
}
注意,在使用join传递代理之前,向客户代理添加了一个值为 "o" 的_fwd上下文。它提示Glacier使用单向调用来转发客户回调。这样比双向调用更加有效。因为所有的回调操作均为void返回值,所以可以单向调用。
服务器的回调为普通的双向调用。这样当出错时可以通知服务器。当客户端出错时,这个对结束客户会话很有用。
一旦客户调用了setCallback,就可以接收聊天室的各种行为通知。下为send实现:
Ice::Long
ChatSessionI::send(const string& message, const Ice::Current&)
{
IceUtil::Mutex::Lock sync(_mutex);
if(_destroy)
{
throw Ice::ObjectNotExistException(__FILE__, __LINE__);
}
if(!_callback)
{
throw InvalidMessageException("You cannot send messages until you joined the chat.");
}
string;
try
{
msg = validateMessage(message);
}
catch(const string& reason)
{
throw InvalidMessageException(reason);
}
return _chatRoom->send(_name, msg);
}
客户要离开聊天室,只要调用 destory.
void
ChatSessionI::destroy(const Ice::Current& c)
{
IceUtil::Mutex::Lock sync(_mutex);
if(_destroy)
{
throw Ice::ObjectNotExistException(__FILE__, __LINE__);
}
try
{
c.adapter->remove(c.id);
if(_callback == 0)
{
_chatRoom->unreserve(_name);
}
else
{
_chatRoom->leave(_name);
}
}
catch(const Ice::ObjectAdapterDeactivatedException&)
{
// No need to clean up, the server is shutting down.
}
_destroy = true;
}