服務(wù)器實(shí)現(xiàn): 服務(wù)器使用C++。注意它的結(jié)構(gòu):類 ChatRoom 實(shí)現(xiàn)了大部分的應(yīng)用邏輯。為了支持推模型與拉模型,服務(wù)器實(shí)現(xiàn)了類ChatSession 和類 PollingChatSession。 ChatRoom 調(diào)用 ChatRoomCallbackAdapter 對(duì)象的 send 函數(shù)來(lái)傳遞客戶消息,該對(duì)象隱藏了兩種模型之間的差異。
ChatRoom 實(shí)現(xiàn):
ChatRoom是一個(gè)普通的C++對(duì)象,而不是一個(gè)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是一個(gè)字符串集合,它存儲(chǔ)已經(jīng)建立回話,但是還沒(méi)有加入聊天室的客戶名。_members存儲(chǔ)當(dāng)前聊天室的所有用戶(已經(jīng)調(diào)用過(guò)join函數(shù)的用戶)。
成員函數(shù) reserve 和 unreserve 維護(hù) _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實(shí)現(xiàn),同join實(shí)現(xiàn)非常類似:
// 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 實(shí)現(xiàn):
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的四個(gè)成員函數(shù),當(dāng)異步調(diào)用完成時(shí),都使用類AMICallback來(lái)接收通知。它的定義如下:
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;
};
當(dāng)用戶回調(diào)操作拋出異常,服務(wù)器立即銷毀客戶會(huì)話,即把該用戶趕出聊天室。這是因?yàn)椋坏┛蛻舻幕卣{(diào)對(duì)象出現(xiàn)了一次異常,它以后也就不可能再正常。
推模式會(huì)話創(chuàng)建:
現(xiàn)在來(lái)看一下會(huì)話創(chuàng)建。推模式的客戶使用Glacier2,所以要使用Glacier2的會(huì)話創(chuàng)建機(jī)制。Glacier2 允許用戶通過(guò)提供一個(gè)Glacier2::SessionManager對(duì)象的代理來(lái)自定義會(huì)話創(chuàng)建機(jī)制。通過(guò)設(shè)置Glacier2.SessionManager屬性來(lái)配置Gloacier2,就可以使用自己的會(huì)話管理器。會(huì)話管理器除了一個(gè)trivial構(gòu)造函數(shù)(設(shè)置聊天室指針),只有一個(gè)操作,create,Glacier2調(diào)用它來(lái)代理應(yīng)用的會(huì)話創(chuàng)建。 create 操作必須返回一個(gè)會(huì)話代理(類型為Glacier2::Session*)。實(shí)現(xiàn)如下:
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;
}
首先調(diào)用一個(gè)簡(jiǎn)單的幫助函數(shù) validateName, 來(lái)檢查傳遞的用戶名是否包含非法字符,并把它轉(zhuǎn)為大寫,然后調(diào)用 reserver函數(shù)把它加到聊天室的_reserved集合中。我們要監(jiān)視這些操作拋出的消息,并把它轉(zhuǎn)化為Glacide2::CannotCreateSessionException異常,即在create操作的異常規(guī)范聲明的異常。
接著實(shí)例化一個(gè)ChatSessionI對(duì)象(見(jiàn)下面)來(lái)創(chuàng)建會(huì)話。注意這個(gè)會(huì)話使用UUID作為對(duì)象標(biāo)識(shí),所以保證標(biāo)識(shí)符唯一。
最后,添加這個(gè)新創(chuàng)建的會(huì)話標(biāo)識(shí),Gllacier2只通過(guò)它來(lái)轉(zhuǎn)發(fā)經(jīng)過(guò)這個(gè)會(huì)話的請(qǐng)求。實(shí)際上,“只轉(zhuǎn)發(fā)經(jīng)過(guò)這個(gè)會(huì)話的并且只到這個(gè)會(huì)話的請(qǐng)求”,這是一種安全的辦法:如果有惡意客戶能猜出另一個(gè)客戶會(huì)話的標(biāo)識(shí),它也不能向別的對(duì)象發(fā)送請(qǐng)求(可能在除了聊天服務(wù)器之外的服務(wù)器上)。如果出錯(cuò),就銷毀剛創(chuàng)建的會(huì)話對(duì)象,這樣避免了資源泄露。
這就是利用Glacier2創(chuàng)建會(huì)話的全部。如果你希望使用Glacier2的認(rèn)證機(jī)制,可以設(shè)置屬性Glacier2.PermissionsVerifier為執(zhí)行認(rèn)證的對(duì)象代理。(Glacier2提供一個(gè)內(nèi)置的權(quán)限驗(yàn)證器,NullPermissionsVerifier,可以檢查用戶名和密碼)。
圖:會(huì)話創(chuàng)建交互圖(略)
ChatSessionI類實(shí)現(xiàn)了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;
構(gòu)造函數(shù)設(shè)置聊天室和用戶名,并把_destroy設(shè)置為False.
由于Glacier2::create操作不允許傳遞代理,必須把創(chuàng)建會(huì)話和設(shè)置回調(diào)分成兩步。這是setCallback的實(shí)現(xiàn);
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傳遞代理之前,向客戶代理添加了一個(gè)值為 "o" 的_fwd上下文。它提示Glacier使用單向調(diào)用來(lái)轉(zhuǎn)發(fā)客戶回調(diào)。這樣比雙向調(diào)用更加有效。因?yàn)樗械幕卣{(diào)操作均為void返回值,所以可以單向調(diào)用。
服務(wù)器的回調(diào)為普通的雙向調(diào)用。這樣當(dāng)出錯(cuò)時(shí)可以通知服務(wù)器。當(dāng)客戶端出錯(cuò)時(shí),這個(gè)對(duì)結(jié)束客戶會(huì)話很有用。
一旦客戶調(diào)用了setCallback,就可以接收聊天室的各種行為通知。下為send實(shí)現(xiàn):
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);
}
客戶要離開(kāi)聊天室,只要調(diào)用 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;
}