??xml version="1.0" encoding="utf-8" standalone="yes"?> 成员_reserverd是一个字W串集合Q它存储已经建立回话Q但是还没有加入聊天室的客户名。_members存储当前聊天室的所有用P已经调用qjoin函数的用P?br> c?ChatRoomCallbackAdapter 注意Q在使用join传递代理之前,向客户代理添加了一个gؓ "o" 的_fwd上下文。它提示Glacier使用单向调用来{发客户回调。这h双向调用更加有效。因为所有的回调操作均ؓvoidq回|所以可以单向调用?br> 服务器的回调为普通的双向调用。这样当出错时可以通知服务器。当客户端出错时Q这个对l束客户会话很有用?br>
Go语言是Google推出的新的一个致力于pȝU的~程语言。很多h说它?C + PythonQ既有c的灵z高效,又有Python的简单易用,它的原则?Simple && Fast。它的语法规则很单。其官方|站上有一个《三天学会Go语言》的教程Q包括三部分Q基本结构,面向对象Qƈ发。一个周末的旉差不多可以掌握其基本面貌。相Ҏ_W三部分“q发”g难接受一些。但是如果你对多U程Q同步,消息队列q些东西很有l验的话Q对它的概念也会Ҏ理解?br>
2.Go语言的面向对?/span>
Go语言在C语言语法的基上,以最z的形式加入了面向对象?br> 关于l承QGo语言没有l承。但是它支持嵌入Q这个有点类g其它语言的mixinQ可以用来模拟ѝ?br> 关于多态:Go 语言的最大特Ҏ它的接口定义。所?#8220;接口”Q就是一l方法的集合。Q何一个类只要实现了一个接口的所有方法,则是该接口的实现者,不需要显式声明实现该接口。所以一个没有Q何方法的I接口可以代表Q何类型?br style="FONT-WEIGHT: bold">
3.Go语言的ƈ?/span>
Go语言提出一个新的概念—Go例程Q有点类gU程Q但是更加轻量,更省资源。Go例程之间的通信方式 ——信道,q是GO 语言的核心概念,有点cM于UNIX的Pipe。在Go语言中,不需要接触线E,锁这些低阶概c?br>
4.Go语言可以用来做什?/span>
Go语言目前最强的是它的网l功能。它的package中已l实C最常见的网l协议和~码处理。Go的官方网站用的是Go语言Q实际上它就是Go的文系lgodoc?br> q没有官方对数据库支持,不过因ؓ通过某种办法可以在Go语言中直接调用C函数Q所以很Ҏ的实现对MYSQL 或?Sqlite q些数据库的支持?br> 至于GUIQ这估计目前q不在设计者的考虑范围之内Q因Z们连Windows都不舍得支持。不q可以通过它的http包和template包等{,已经构成了一个WEB框架Q可以用来实现WEB GUI的开发,使用Go写一个带有\径分zHttp服务器也几行代码的事?br>
]]>
服务器用C++。注意它的结构:c?ChatRoom 实现了大部分的应用逻辑。ؓ了支持推模型与拉模型Q服务器实现了类ChatSession 和类 PollingChatSession?ChatRoom 调用 ChatRoomCallbackAdapter 对象?send 函数来传递客h息,该对象隐藏了两种模型之间的差异?br>
ChatRoom 实现Q?br>
ChatRoom是一个普通的C++对象Q而不是一个Servant.
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;
成员函数 reserve ?unreserve l护 _reserved 集合?/p>
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操作d用户到聊天室?/p>
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实现Q同join实现非常cMQ?/p>
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;
}
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 实现Q?
{
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的四个成员函敎ͼ当异步调用完成时Q都使用cAMICallback来接攉知。它的定义如下:
{
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;
};
推模式会话创?/span>Q?br>
现在来看一下会话创建。推模式的客户用Glacier2Q所以要使用Glacier2的会话创建机制。Glacier2 允许用户通过提供一个Glacier2::SessionManager对象的代理来自定义会话创建机制。通过讄Glacier2.SessionManager属性来配置Gloacier2Q就可以使用自己的会话管理器。会话管理器除了一个trivial构造函敎ͼ讄聊天室指针)Q只有一个操作,createQGlacier2调用它来代理应用的会话创建?create 操作必须q回一个会话代理(cd为Glacier2::Session*Q。实现如下:
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, 来检查传递的用户名是否包含非法字W,q把它{为大写,然后调用 reserver函数把它加到聊天室的_reserved集合中。我们要监视q些操作抛出的消息,q把它{化ؓGlacide2::CannotCreateSessionException异常Q即在create操作的异常规范声明的异常?br> 接着实例化一个ChatSessionI对象Q见下面Q来创徏会话。注意这个会话用UUID作ؓ对象标识Q所以保证标识符唯一?br> 最后,dq个新创建的会话标识QGllacier2只通过它来转发l过q个会话的请求。实际上Q?#8220;只{发经q这个会话的q且只到q个会话的请?#8221;Q这是一U安全的办法Q如果有恶意客户能猜出另一个客户会话的标识Q它也不能向别的对象发送请求(可能在除了聊天服务器之外的服务器上)。如果出错,销毁刚创徏的会话对象,q样避免了资源泄霌Ӏ?br> q就是利用Glacier2创徏会话的全部。如果你希望使用Glacier2的认证机Ӟ可以讄属性Glacier2.PermissionsVerifier为执行认证的对象代理。(Glacier2提供一个内|的权限验证器,NullPermissionsVerifierQ可以检查用户名和密码)?br> 图:会话创徏交互图(略)
ChatSessionIcdCChatSession接口?br>
{
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;
׃Glacier2::create操作不允怼递代理,必须把创Z话和讄回调分成两步。这是setCallback的实玎ͼ
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);
}
一旦客戯用了setCallbackQ就可以接收聊天室的各种行ؓ通知。下为send实现Q?/p>
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.
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;
}
]]>
需?br>
1Q一个典型的聊天室应用,使用客户?服务器架构,客户发送消息到中心服务器,然后Q消息发送给其它客户?br> 2Q尽量减服务器理Q甚臛_以不需要?br> 3Q通信必须安全Q通过公共|络时必要保护个h隐私?br> 4Q当客户端和服务器有防火墙保护时也能正常q行Q客L不用修改它的|络或者防火墙讄?br> 5Q客L可以在各U^C利用多种语言实现Q比如说利用Web览器作为客L?br> 6Q客L可能|络带宽有限Q所以应该尽量减网l流量?br> 只支持单个聊天室。(多个聊天室只是代码多了点Qƈ没有增加M隑ֺQ?br>
设计
在本文中Q将会演C多U客L的设计和实现。包括:
1QC++命o行客LQ?br> 2QJAVA SWing GUI客户端;
3Q?NET WPF客户端;
4QPHP|页客户端;
5QSilverlight |页客户端;
注意Q对于ICE3.3, PHP和Ruby只提供了客户端的Run Time。我们在考虑当连接的客户如何从服务器获得消息Ӟ必须要想到这一炏V对于消息发布,有两U通信模型Q?br> 1Q推模型Q略
2Q拉模型Q略
推模型比较简单,Ҏ实现Q我们的Chat 客户端中QC++QJava, .Net, Silverlight(0.3)都用该模型。PHP客户端用拉模型?br>
推模型定?br> 每个客户端中提供一?ChatRoomCallback cd的ICE对象到服务器。当发生事gӞ服务器调用该对象的操作通知客户。SLICE定义如下Q?br>
module Chat
{
// Implemented by clients
interface ChatRoomCallback
{
["ami"] void init(Ice::StringSeq users);
["ami"] void join(long timestamp, string name);
["ami"] void leave(long timestamp, string name);
["ami"] void send(long timestamp, string name, string message);
};
};
1Q当用户首次q接到聊天室Ӟ服务器调?init 操作. users参数告诉用户目前q接到聊天室的所有用户信息?br> 2Q有用户q接到聊天室Ӟ服务器调?join 操作?br> 3Q有用户断开q接Ӟ服务器调?nbsp;leave 操作?br> 4Q有用户发送消息时Q服务器调用 send 操作?br> 注意设计使用异步事g。元数据指o ["ami"] 标明服务器异步调用回调操作。当客户端行为异常时Q这Ҏ务器是一个保护:客户端可能长旉dQ服务器调用期间不会因此失去对线E的控制?br>
与防火墙协作
?.....
Glacer2是ICE针对q种情况的预建的解决ҎQ它扮演一个服务器前端。Glacer2h以下特征Q?br> 1Q支持会话概念,API支持认证机制Q可实现自定义的会话创徏和认证?br> 2Q单个Glacer2可进行Q意数量的服务器和客户端{发。服务器只要有一个端口接受外来连接,而不用管具体服务器个数?br> 3Q对于具有防火墙的客LQ服务器也可调用其提供的回调?br>
因ؓ Glacer2会话概念是面向连接的Q只有当客户端同Glacer2的连接打开Ӟ更精的_同Glacer2保持一个激zȝ会话Ӟ服务器才可以对客戯行回调。换句话_当客L同Glacer2失去q接QGlacer2自动销毁会话。ؓ了阻止客L到Glacer2的连接被意外关闭Q客L必须要禁用ACMQAutomic Connection Management, 自动q接理Q。而且QGlacer2通常寚w旉I闲的会话设|超时。当聊天室长旉没有动作ӞZ防止Glacer2销毁会话,客户端必d期性进行激z,比如Q调?ice_ping, 来对Glacer2的会话超时进行重|?br>
Chat客户端通过服务器提供的 ChatSession接口来和服务器通信?ChatSession 从Glacer2::Sessionz?br>
module Chat
{
exception InvalidMessageException
{
string reason;
};
interface ChatSession extends Glacier2::Session
{
void setCallback(ChatRoomCallback* cb);
["ami"] long send(string message) throws InvalidMessageException;
};
};
q就是推模型QChat客户端调用ChatSession的send来发送消息,服务器调用每一个客LChatRoomCallback的send操作q行分发?br>
拉模型定?br>
TODO
]]>
]]>
]]>
对于使用U程池的Reactor模式Q针Ҏ一个SOCKET句柄的事件处理器handler可能被分zֈ不同的线E当中,q就要求handler的每一个操作都是线E安全的?br> 可以使用一U办法一个handler的操作只能分zֈ一个线E中Qؓ每一个handler讑֮一个线E所有者テQ,一开始テQؓI,则每个线E都可以分派Q第一ơ分z之后,则设定ؓ该线EテQ,以后只分zֈ该线E中。这P可以保证handler操作的单U程性,化以后handler的具体实现。这个テQ也可以灉|讄Q以适应具体事务的要求?br> 但这样ƈ不能保证handlerd无锁Q因会有两个U程会出现竟争,除了q个事g处理U程以外Q还有事件分zE(即事件侦听线E)。对于这个问题的解决办法如下Qؓ每个handler讑֮一个原子计敎ͼ事g分派U程在分z事件前Q首先设定该原子计数Q若讄p|Q表明此时正有其它线E在处理该handlerQ则q不分派该事Ӟ而是它|于一个pending队列中,{待以后分派?br> q有一U简单的ҎQ就是将该handler直接挂vQ处理完后才允许q行事g分派?
]]>