[T] ICE实例学习:Let's Chat! (2) 实现服务器

[T] ICE实例学习:Let's Chat! (2) 实现服务器


服务器实现:

       服务器使用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
}

 

 

你可能感兴趣的:([T] ICE实例学习:Let's Chat! (2) 实现服务器)