服務器實現: 服務器使用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;
}