ESFramework 4.0 快速上手
时间:2010-09-11 来源:zhuweisky
(在阅读该文之前,请先阅读 ESFramework 4.0 概述 ,会对本文的理解更有帮助。)
ESFramework/ESPlatform 4.0 的终极目标是为百万级的用户同时在线提供支持,因为强大,所以使用也较为复杂,配置也较多。但是如果我们的应用只是一个中小型的通信应用(同时在线5000人以下),直接使用ESPlatform就有点显得杀鸡用牛刀了。ESPlus.Rapid提供了一种快速的方式,来解决类似中小型的通信应用,以最简洁的方式来使用ESFramework。
/// <param name="customizeHandler">服务器通过此接口来处理客户端提交给服务端(且最终目的地是服务端)的非转发消息。</param>
void Initialize(int port, ICustomizeInfoBusinessHandler customizeHandler);
/// <summary>
/// 完成服务端引擎的初始化,并启动服务端引擎。
/// </summary>
/// <param name="port">用于提供tcp通信服务的端口</param>
/// <param name="customizeHandler">服务器通过此接口来处理客户端提交给服务端(且最终目的地是服务端)的非转发消息。</param>
/// <param name="friendsManager">服务器通过此接口来获取好友关系,如此,比如当某用户上下线时,服务器会自动发送通知给其相关好友。</param>
/// <param name="groupManager">服务器通过此接口来获取某个分组内的成员列表信息,如此,可以发送广播信息。</param>
void Initialize(int port, ICustomizeInfoBusinessHandler customizeHandler, IFriendsManager friendsManager,IGroupManager groupManager);
/// <summary>
/// ConfigMainServerForm 可以为服务端提供默认的主窗体,该窗体用于显示在线用户相关数据、连接数、线程数等信息。
/// </summary>
/// <param name="form">默认的主窗体</param>
void ConfigMainServerForm(MainServerForm form);
/// <summary>
/// Close 关闭服务端引擎。
/// </summary>
void Close();
}
/// <param name="serverIP">服务器的IP地址</param>
/// <param name="serverPort">服务器的端口</param>
/// <param name="basicHandler">基础处理器,用于处理服务器发出的与状态相关的通知</param>
/// <param name="customizeInfoBusinessHandler">自定义处理器,用于处理服务器或其它用户发送过来的消息</param>
void Initialize(string userID, string serverIP, int serverPort, IBasicBusinessHandler basicHandler, ICustomizeInfoBusinessHandler customizeHandler);
/// <summary>
/// 关闭客户端通信引擎。
/// </summary>
void Close();
}
rapidPassiveEngine.Initialize方法将连接本地的9018端口,并且后面两个参数都传递了mainForm,表明mainForm即实现了IBasicBusinessHandler接口也实现了ICustomizeInfoBusinessHandler接口。
二.客户端:如何向服务端或其它在线用户发送消息
以下是ESPlus.Application.CustomizeInfo.Passive.ICustomizeInfoOutter接口定义:
/// <summary>/// 该接口用于向服务器或其它在线用户发送自定义信息或广播。
/// zhuweisky 2010.08.17
/// </summary>
public interface ICustomizeInfoOutter :IOutter
{
/// <summary>
/// 向服务器发送文本信息。
/// </summary>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">文本信息</param>
void Send(int informationType ,string info);
/// <summary>
/// 向服务器发送二进制信息。
/// </summary>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">二进制信息</param>
void Send(int informationType, byte[] info);
/// <summary>
/// 向在线用户targetUserID发送文本信息。如果目标用户不在线,则消息被丢弃。
/// </summary>
/// <param name="targetUserID">接收消息的目标用户ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">文本信息</param>
void Send(string targetUserID, int informationType, string info);
/// <summary>
/// 向在线用户targetUserID发送二进制信息。如果目标用户不在线,则消息被丢弃。
/// </summary>
/// <param name="targetUserID">接收消息的目标用户ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">二进制信息</param>
void Send(string targetUserID, int informationType, byte[] info);
/// <summary>
/// 向目标组内所有在线用户广播文本信息。如果groupID为null,表示向(当前连接的Master服务器上的)所有在线用户广播。
/// </summary>
/// <param name="groupID">接收广播信息的目标组ID,如果为null,表示向(当前连接的Master服务器上的)所有在线用户广播。</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">文本信息</param>
void BroadcastInGroup(string groupID, int informationType, string info);
/// <summary>
/// 向目标组内所有在线用户广播二进制信息。如果groupID为null,表示向(当前连接的Master服务器上的)所有在线用户广播。
/// </summary>
/// <param name="groupID">接收广播信息的目标组ID,如果为null,表示向(当前连接的Master服务器上的)所有在线用户广播。</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">二进制信息</param>
void BroadcastInGroup(string groupID, int informationType, byte[] info);
}
该接口中定义了三种方法:向其他的在线用户发送信息、向服务器发送信息、向某个组内的成员广播信息。
/// 服务端主动向用户发送/投递自定义信息或广播的控制接口。
/// </summary>
public interface ICustomizeInfoController
{
/// <summary>
/// 向ID为userID的在线用户发送文本信息。如果用户不在线,则直接返回。
/// </summary>
/// <param name="userID">接收消息的用户ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">文本信息</param>
void Send(string userID, int informationType, string info);
/// <summary>
/// 向ID为userID的在线用户发送二进制信息。如果用户不在线,则直接返回。
/// </summary>
/// <param name="userID">接收消息的用户ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">二进制信息</param>
void Send(string userID, int informationType, byte[] info);
/// <summary>
/// 向ID为userID的在线用户投递文本信息。如果用户不在线,则直接返回。
/// </summary>
/// <param name="userID">接收消息的用户ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">文本信息</param>
void Post(string userID, int informationType, string info);
/// <summary>
/// 向ID为userID的在线用户投递二进制信息。如果用户不在线,则直接返回。
/// </summary>
/// <param name="userID">接收消息的用户ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">二进制信息</param>
void Post(string userID, int informationType, byte[] info);
/// <summary>
/// 向目标组内的在线用户发送文本广播信息。
/// </summary>
/// <param name="groupID">接收消息的组ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">文本广播信息</param>
void SendBroacast(string groupID, int informationType, string info);
/// <summary>
/// 向目标组内的在线用户发送二进制广播信息。
/// </summary>
/// <param name="groupID">接收消息的组ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">二进制广播信息</param>
void SendBroacast(string groupID, int informationType, byte[] info);
/// <summary>
/// 向目标组内的在线用户投递文本广播信息。
/// </summary>
/// <param name="groupID">接收消息的组ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">文本广播信息</param>
void PostBroacast(string groupID, int informationType, string info);
/// <summary>
/// 向目标组内的在线用户投递二进制广播信息。
/// </summary>
/// <param name="groupID">接收消息的组ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">二进制广播信息</param>
void PostBroacast(string groupID, int informationType, byte[] info);
}
Send和Post的区别是,Send是同步发送的,Post是异步发送的。另外,ICustomizeInfoController接口还可以向某个组内的所有成员广播消息。
四.客户端:接收到消息后如何处理它们?
客户端可以接收来自服务端的消息和来自其它客户端的消息。接着刚才的例子,在aa02那一方,客户端引擎会调用ICustomizeInfoBusinessHandler接口的HandleInformation方法来处理来自aa01的"Hello"文本信息。这也是为什么需要在rapidPassiveEngine.Initialize方法中传入ICustomizeInfoBusinessHandler引用的原因了。
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">文本信息</param>
void HandleInformation(string sourceUserID, int informationType, string info);
/// <summary>
/// 处理来自其他用户的二进制信息。
/// </summary>
/// <param name="sourceUserID">发出信息的用户ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">二进制信息</param>
void HandleInformation(string sourceUserID, int informationType, byte[] info);
/// <summary>
/// 处理来自服务器的文本信息。
/// </summary>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">文本信息</param>
void HandleInformationFromServer(int informationType, string info);
/// <summary>
/// 处理来自服务器的二进制信息。
/// </summary>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">二进制信息</param>
void HandleInformationFromServer(int informationType, byte[] info);
/// <summary>
/// 处理来自其他用户的文本广播信息。
/// </summary>
/// <param name="broadcasterID">发出广播信息的用户ID</param>
/// <param name="groupID">接收广播信息的组ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">文本广播信息</param>
void HandleBroadcast(string broadcasterID, string groupID, int informationType, string info);
/// <summary>
/// 处理来自其他用户的二进制广播信息。
/// </summary>
/// <param name="broadcasterID">发出广播信息的用户ID</param>
/// <param name="groupID">接收广播信息的组ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">二进制广播信息</param>
void HandleBroadcast(string broadcasterID, string groupID, int informationType, byte[] info);
/// <summary>
/// 处理来自服务器的文本广播信息。
/// </summary>
/// <param name="groupID">接收广播信息的组ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">文本广播信息</param>
void HandleBroadcastFromServer(string groupID, int informationType, string info);
/// <summary>
/// 处理来自服务器的二进制广播信息。
/// </summary>
/// <param name="groupID">接收广播信息的组ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">二进制广播信息</param>
void HandleBroadcastFromServer(string groupID, int informationType, byte[] info);
}
五.服务端:如何处理来自客户端的消息?
服务端接收到的来自客户端的消息可以分为两类,一类是交由服务器中转的消息,如刚才例子中的aa01发给aa02的消息(如果P2P通道可用,这样的消息将可以不再经过服务器中转);另外一类是真正由服务器的业务逻辑处理的消息。关于第一类消息,ESPlus.Rapid框架内部已经自动进行了转发,我们这里关心的是第二类消息的处理。服务端通过IRapidServerEngine.Initialize方法传入的ESPlus.Application.CustomizeInfo.Server.ICustomizeInfoBusinessHandler接口来处理第二类消息。
ESPlus.Application.CustomizeInfo.Server.ICustomizeInfoBusinessHandler接口定义如下:
/// <summary>/// 服务端通过此接口来处理来自客户端的自定义信息。 /// </summary>
public interface ICustomizeInfoBusinessHandler
{
/// <summary>
/// 处理来自客户端的自定义文本信息。
/// </summary>
/// <param name="sourceUserID">发送该信息的用户ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">文本信息</param>
void HandleInformation(string sourceUserID,int informationType , string info);
/// <summary>
/// 处理来自客户端的自定义二进制信息。
/// </summary>
/// <param name="sourceUserID">发送该信息的用户ID</param>
/// <param name="informationType">自定义信息类型</param>
/// <param name="info">二进制信息</param>
void HandleInformation(string sourceUserID, int informationType, byte[] info);
}
六.服务端:我要踢掉某个在线用户,该怎么办?
IRapidServerEngine暴露的BasicController属性可以让服务端的业务逻辑直接进行一些控制动作,比如踢人和在某个组内广播消息。
ESPlus.Application.Basic.Server.IBasicController接口定义如下:
/// <summary>/// 直接在从服务端发出相关控制指令(如踢人等)。
/// </summary>
public interface IBasicController
{
/// <summary>
/// 将目标用户从服务器中踢出,并关闭对应的连接。
/// </summary>
void KickOut(string targetUserID);
/// <summary>
/// 将目标消息广播给(当前连接的Master服务器上的)所有在线用户。
/// </summary>
/// <param name="msg">要广播的消息</param>
void Broadcast(string msg);
}
/// IBasicOutter 用于客户端发送Basic信息。
/// </summary>
public interface IBasicOutter :IOutter
{
/// <summary>
/// 获取自己的IPE。
/// </summary>
/// <returns>通常是经过NAT之后的IPE</returns>
IPEndPoint GetMyIPE();
/// <summary>
/// 获取(当前连接的Master服务器上的)所有在线的用户列表。
/// </summary>
List<string> GetAllOnlineUsers();
/// <summary>
/// 命令(当前连接的Master)服务端将目标用户踢出。如果目标用户不在线或者不在当前连接的Master服务器上,则直接返回。
/// </summary>
/// <param name="targetUserID">要踢出的用户ID</param>
void KickOut(string targetUserID);
/// <summary>
/// 将目标消息广播给(当前连接的Master服务器上的)所有在线用户。
/// </summary>
/// <param name="msg">要广播的消息</param>
void Broadcast(string msg);
/// <summary>
/// 向服务器发送心跳消息。被框架ESPlus.Application.Basic.Passive.HeartBeater使用。
/// </summary>
void SendHeartBeatMessage() ;
}
注意,IBasicOutter也提供了广播消息的Broadcast方法,不过这个Broadcast方法是向所有的在线用户广播信息,而ICustomizeInfoOutter的BroadcastInGroup方法是对某个组内的所有成员进行广播,两者是有区别的。
八.客户端:我如何得知好友上线/下线消息?
IRapidPassiveEngine在初始化Initialize方法中,传入的IBasicBusinessHandler接口用来处理来自服务器的广播消息之外,还处理来自服务端的众多通知,比如,好友上下线、自己超时掉线等等。
ESPlus.Application.CustomizeInfo.Passive.IBasicBusinessHandler接口:
/// <summary>/// 客户端必须实现此接口,对服务端给出的相关通知作出正确的反映。
/// </summary>
public interface IBasicBusinessHandler : IBusinessHandler
{
/// <summary>
/// OnFriendConnected 好友上线。 服务端通过IFriendsManager发现好友。
/// </summary>
void OnFriendConnected(string friendID);
/// <summary>
/// OnFriendOffline 好友掉线。 服务端通过IFriendsManager发现好友。
/// </summary>
void OnFriendOffline(string friendID);
/// <summary>
/// OnBeingPushedOut 被同名用户挤掉线。此时,客户端引擎已被Dispose。
/// 发生于RelogonMode.ReplaceOld。
/// </summary>
void OnBeingPushedOut();
/// <summary>
/// OnTimeoutOffline 心跳超时掉线。此时,客户端引擎已被Dispose。
/// </summary>
void OnTimeoutOffline();
/// <summary>
/// OnHaveLogonNotify 当同名的用户已经登录,而且当前连接被忽略(已被服务端关闭)时调用此方法。此时,客户端引擎已被Dispose。
/// 发生于RelogonMode.IgnoreNew。
/// </summary>
void OnHaveLogonNotify();
/// <summary>
/// OnBeingKickedOut 被服务端踢出掉线。此时,客户端引擎已被Dispose。
/// </summary>
void OnBeingKickedOut();
/// <summary>
/// 处理来自其他用户的广播信息。
/// </summary>
/// <param name="broadcasterID">发出广播的用户ID</param>
/// <param name="msg">广播信息</param>
void HandleBroadcast(string broadcasterID, string msg);
/// <summary>
/// 处理来自服务器的广播信息。
/// </summary>
/// <param name="msg">广播信息</param>
void HandleBroadcastFromServer(string msg);
}
九.客户端:如何知道自己已经掉线?
IRapidPassiveEngine暴露了TcpPassiveEngine属性,ITcpPassiveEngine是RapidPassiveEngine内部使用的真正的核心引擎,通过ITcpPassiveEngine发布的事件,我们可以得知自己掉线/重连开始/重连成功(失败)等相关通知。ITcpPassiveEngine接口一些重要的事件和属性的定义摘抄如下:
/// <summary>/// 当客户端与服务器的TCP连接断开时,将触发此事件。
/// </summary>
event CbGeneric ConnectionInterrupted;
/// <summary>
/// 自动重连开始时,触发此事件。
/// </summary>
event CbGeneric ConnectionRebuildStart;
/// <summary>
/// 自动重连成功后,触发此事件。
/// </summary>
event CbGeneric ConnectionRebuildSucceed;
/// <summary>
/// 自动重连超过最大重试次数时,表明重连失败,将触发此事件。
/// </summary>
event CbGeneric ConnectionRebuildFailure;
/// <summary>
/// 当前是否处于连接状态。
/// </summary>
bool Connected { get; }
/// <summary>
/// 当与服务器断开连接时,是否自动重连。
/// </summary>
bool AutoReconnect { get; set; }
关于ITcpPassiveEngine接口的更多信息,可以参见ESFramework4.0.chm帮助文档。
十.服务端:如何得到在线用户的相关信息?
IRapidServerEngine暴露的UserManager属性可以让我们获取在线用户的一些基本信息。
ICoreUserManager接口定义如下:
public interface ICoreUserManager{
/// <summary>
/// 获取目标在线用户的基础信息。
/// </summary>
/// <param name="userID">目标用户的ID</param>
/// <returns>如果目标用户不在线,则返回null</returns>
UserData GetUserData(string userID);
/// <summary>
/// 获取所有在线用户的ID列表。
/// </summary>
List<string> GetOnlineUserList();
/// <summary>
/// 如果是基于tcp引擎,则当tcp连接上接收或发送数据抛出异常时,将关闭连接,并触发此事件。
/// </summary>
event CbGeneric<UserData> SomeOneDisconnected;
/// <summary>
/// 如果是基于tcp引擎,当接收到新连接上的第一个消息时,将触发此事件。
/// </summary>
event CbGeneric<UserData> SomeOneConnected;
}
ICoreUserManager暴露了够用的与在线用户相关信息,如果想获取更多与用户管理相关的功能,可以将ICoreUserManager向上强转为IUserManager接口,关于IUserManager更多信息,可以参考ESFramework4.0.chm帮助文档。
到这里,你也许已经发现了一个小秘密,这也是ESPlus整个框架内部遵守的规则:凡是以"Outter"结尾的接口/组件(如IBasicOutter和ICustomizeInfoOutter接口),都是框架已经实现的,并暴露出来给你在客户端直接使用的;凡是以"Controller"结尾的接口/组件(如IBasicController接口),也是框架已经实现的,并暴露出来给你在服务端进行控制调用的;凡是以"Handler"结尾的(如IBasicBusinessHandler和ICustomizeInfoBusinessHandler接口),无论是服务端的还是客户端的,框架都没有提供"有意义的"实现的(那些以Empty开头的Handler相当于是占位符,它们实现了Handler接口,但本身不做任事情。NullObject模式,呵呵),你需要实现这些接口,并挂接到框架上(比如通过引擎的Initialize方法)以注入你的业务处理逻辑(IOC模式的运用),这样,当对应的消息来临时,框架会自动调用Handler的对应的方法。
通常,对于使用ESPlus.Rapid来进行ESFramework快速开发来说,上述的介绍已经够用了,如果想了解更多的信息和使用ESFramework/ESPlus/ESPlatform的高级特性和更强大的功能,可能需要一段时间的专业培训才可以。
最后,给出demo源码和相关文档的下载:
1.ESFramework4.0 Rapid Demo
本demo是一个简单的IM聊天程序,并且展示了掉线通知、断线重连等功能。
2.ESFramework4.0 帮助文档
帮助文档包括:ESFramework4.0.chm、ESPlus1.0.chm、ESFramework.SL.chm(在SilverLight中使用ESFramwork)。
任何问题,请联系[email protected]。that's all,thanks.