ESFramework 开发手册(02) -- 基础功能与状态通知

来源:互联网 发布:东亚文化圈知乎 编辑:程序博客网 时间:2024/06/03 10:05

      本文介绍ESFramework 开发手册(00) -- 概述一文中提到的四大武器的第二个:基础功能与状态改变通知。

      在解决了发送信息和处理信息之后,还有一些基础功能是很多分布式通信系统都需要用到的,比如,查询某个用户是否在线、获取在线的好友列表、当好友上下线时得到通知,等等。ESPlus.Application.Basic命名空间下的组件,为我们解决了这些基础问题。

 

1.客户端

      客户端通过调用ESPlus.Application.Basic.Passive.IBasicOutter接口对应的方法以及预定其相关的事件,就可以完成基础功能或得到相关状态改变通知。

      我们可以从ESPlus.Rapid.IRapidPassiveEngine暴露的BasicOutter属性来获取IBasicOutter引用。

   public interface IBasicOutter :IOutter            {     /// <summary>        ///  当好友上线时,触发此事件。参数为好友的UserID        /// </summary>                event CbGeneric<string> FriendConnected;        /// <summary>        ///  当好友下线时,触发此事件。参数为好友的UserID        /// </summary>        event CbGeneric<string> FriendOffline;        /// <summary>        ///  当组友上线时,触发此事件。参数为组友的UserID        /// </summary>        event CbGeneric<string> GroupmateConnected;        /// <summary>        ///  当组友上线时,触发此事件。参数为组友的UserID        /// </summary>        event CbGeneric<string> GroupmateOffline;        /// <summary>        ///  当自己被同名用户挤掉线时,触发此事件。此时,客户端引擎已被Dispose。        /// </summary>        event CbGeneric BeingPushedOut;     /// <summary>        ///  当自己心跳超时掉线时,触发此事件。此时,客户端引擎已被Dispose。        /// </summary>        event CbGeneric TimeoutOffline;     /// <summary>        ///  当自己被服务端踢出掉线时,触发此事件。此时,客户端引擎已被Dispose。        /// </summary>        event CbGeneric BeingKickedOut;     /// <summary>        /// 当RelogonMode为IgnoreNew,再次使用同名用户登录时,触发此事件。此时,客户端引擎已被Dispose。              /// </summary>        event CbGeneric HadLogon;              /// <summary>        /// 客户端登陆验证。IRapidPassiveEngine会在初始化时,自动调用该方法来验证用户账号密码。        /// </summary>        /// <param name="systemToken">系统标志。用于验证客户端是否与服务端属于同一系统。</param>        /// <param name="password">登陆密码</param>              LogonResponse Logon(string systemToken, string password);        /// <summary>        /// 获取自己的IPE。        /// </summary>        /// <returns>通常是经过NAT之后的IPE</returns>        IPEndPoint GetMyIPE();     /// <summary>        /// 获取当前AS上的所有在线的用户列表。        /// </summary>              List<string> GetAllOnlineUsers();        /// <summary>        /// 获取所有在线的好友列表。        /// </summary>              List<string> GetAllOnlineFriends();        /// <summary>        /// 获取好友列表。        /// </summary>              List<string> GetFriends();        /// <summary>        /// 获取在线的组友列表。        /// </summary>                      /// <returns>在线组好友的UserID列表</returns>        List<string> GetAllOnlineGroupmates();                              /// <summary>        /// 查询用户是否在线。        /// </summary>               bool IsUserOnline(string userID);        /// <summary>        /// ping服务器。在应用层模拟ping,比普通的ICMP的ping大一些(如8-10ms)。        /// </summary>        /// <returns>ping耗时,单位毫秒</returns>        int Ping();        /// <summary>        /// ping其他在线用户。在应用层模拟ping,比普通的ICMP的ping大一些(如8-10ms)。        /// 如果目标用户不在线,将抛出Timeout异常。        /// </summary>        /// <param name="targetUserID">要Ping的目标用户ID</param>        /// <returns>ping耗时,单位毫秒</returns>        int Ping(string targetUserID);        /// <summary>        /// 命令服务端将目标用户踢出。如果目标用户不在当前AS上,则直接返回。        /// </summary>        /// <param name="targetUserID">要踢出的用户ID</param>        void KickOut(string targetUserID);        /// <summary>        /// 向服务器发送心跳消息。被框架ESPlus.Application.Basic.Passive.HeartBeater使用。        /// </summary>        void SendHeartBeatMessage() ;                 }

状态改变事件通知

      首先,我们看看IBasicOutter暴露的几个事件,这几个事件都是当自己或好友的状态发生改变时触发的。

  • FriendConnected、FriendOffline 、GroupmateConnected、GroupmateOffline 四个事件用于在好友与组友上下线时,通知当前的客户端用户的。所谓组友Groupmate,就是同属一个组(比如QQ群)的成员。至于服务端是如何知道一个用户有哪些好友和有哪些组友了,请参见好友与组。
  • BeingPushedOut、TimeoutOffline、BeingKickedOut、HadLogon 四个事件用于在自己的状态变化时得到通知的。BeingPushedOut和HadLogon事件是否能触发、以及在什么条件下触发,取决于服务端设置的重登陆模式的策略。关于重登陆模式的更多内容可以参见重登陆模式。

基础API

     接下来,我们看看IBasicOutter的几个方法。

  • Logon方法用于在登录时验证用户密码。该方法会在客户端Rapid引擎初始化时被引擎自动调用,所以,在使用Rapid引擎时,我们通常不需要手动调用它。如果有的系统需要验证除了密码之外更多的信息,那么可以通过systemToken参数进行传递这些额外信息。Logon方法返回类型为LogonResponse,其属性LogonResult是一个枚举,表示了登录结果。如果LogonResult为Succeed,表示登录成功;如果LogonResult为HadLoggedOn,表示该账号已经在其它地方登录;如果LogonResult为Failed,则表示验证账号密码没有通过,没有通过的原因由LogonResponse的FailureCause属性指明。
  • GetAllOnlineFriends、GetAllOnlineGroupmates用于获取所有在线的好友和组友,如果我们的系统需要支持好友或组友,那么,通常我们会在客户端Rapid引擎初始化成功后,调用这两个方法以初始化在线好友列表和在线组友列表,并且结合预定前面的FriendConnected、FriendOffline 、GroupmateConnected、GroupmateOffline 这四个事件,那么,在客户端实例运行的整个生命期内,就可以实时地知道每个好友和组友的在线状态了。
  • Ping方法,用于获取当前客户端到服务端或到另一个在线客户端的消息来回的耗时,由于其是在应用层来模拟类似ICMP的ping,所以这个方法返回的值通常比ICMP的ping大一些。尽管如此,在一些应用中,该Ping的结果还是有一些参考价值的。
  • 有时,我们需要命令服务器将一些恶意的用户从服务端踢出(断开其连接),那么就可以调用KickOut方法,被踢出的客户端将会触发上述的BeingKickedOut事件。        
  • SendHeartBeatMessage方法用于向服务器发送心跳消息。如果我们使用的是Rapid引擎,那么框架会自动发送心跳消息,所以,我们通常不需要手动调用该方法。关于心跳消息的更多内容可以参见心跳机制。

TCP连接状态

     Basic空间提供了一部分基础功能,还有另一部分很重要的基础功能需要涉及到客户端的TCP引擎(因为客户端Rapid引擎内部使用的正是基于二进制的TCP引擎),我们在这里也一并介绍一下。客户端如何知道自己与服务器的TCP连接的状态变化了?ESFramework.Engine.Tcp.Passive.ITcpPassiveEngine 的几个事件和属性来获取这些信息。
  我们可以从ESPlus.Rapid.IRapidPassiveEngine暴露的TcpPassiveEngine属性来获取ITcpPassiveEngine引用。

    public interface ITcpPassiveEngine:IPassiveEngine    {      /// <summary>        /// 当客户端与服务器的TCP连接断开时,将触发此事件。        /// </summary>        event CbGeneric ConnectionInterrupted;     /// <summary>        /// 自动重连开始时,触发此事件。        /// </summary>        event CbGeneric ConnectionRebuildStart;      /// <summary>        /// 自动重连成功后,触发此事件。        /// </summary>        event CbGeneric ConnectionRebuildSucceed;      /// <summary>        /// 自动重连超过最大重试次数时,表明重连失败,将触发此事件。        /// </summary>        event CbGeneric ConnectionRebuildFailure;     /// <summary>        /// Sock5代理服务器信息。如果不需要代理,则设置为null。        /// </summary>        Sock5ProxyInfo Sock5ProxyInfo { get; set; }               /// <summary>        /// 当前是否处于连接状态。        /// </summary>        bool Connected { get; }        /// <summary>        /// 当与服务器断开连接时,是否自动重连。        /// </summary>        bool AutoReconnect { get; set; }  }

      注释已经很好的说明了每个事件和属性的用途,这里就不赘述了。

      值得一提的是,ConnectionRebuildSucceed事件,当网络恢复,TCP重连成功时,将会触发此事件。但对使用Rapid引擎的开发人员来说,这并不是全部。Rapid客户端引擎(ESPlus.Rapid.IRapidPassiveEngine),会在ConnectionRebuildSucceed事件触发时,自动登录服务器重新验证用户账号和密码,并触发RelogonCompleted事件。RelogonCompleted事件的参数为LogonResult,表明了重新登录验证的结果。而且,如果验证失败,与服务器的连接将会再次断开,且后续不会再自动重连。所以,当开发人员在进行二次开发时,要依据IRapidPassiveEngine的RelogonCompleted事件来作为重连成功/失败的依据。

 

2.服务端

基础控制

  Basic的服务端就相当简单了。首先,我们可以通过ESPlus.Application.Basic.Server.IBasicController的KickOut方法来在服务端进行踢人操作。
  我们可以从ESPlus.Rapid.IRapidServerEngine暴露的BasicController属性来获取IBasicController引用。

登录验证

  刚刚我们提到客户端可以调用IBasicOutter的Logon方法进行登陆验证,那么这个验证服务端是在哪里做的了?服务端正是通过ESPlus.Application.Basic.Server.IBasicHandler的VerifyUser方法来验证用户账号密码的。

    public interface IBasicHandler :IBusinessHandler            {     /// <summary>        /// 客户端登陆验证。        /// </summary>                /// <param name="userID">登陆用户账号</param>        /// <param name="systemToken">系统标志。用于验证客户端是否与服务端属于同一系统。</param>        /// <param name="password">登陆密码</param>     ///<param name="failureCause">如果登录失败,该out参数指明失败的原因</param>        /// <returns>如果密码和系统标志都正确则返回true;否则返回false。</returns>        bool VerifyUser(string systemToken, string userID, string password ,out string failureCause);    }

      请注意,如果账号密码验证不通过,可以通过failureCause参数返回不通过的原因。failureCause的值将被传递并赋值给Logon方法返回的LogonResponse的FailureCause属性。
  同上一章讲到的ICustomizeHandler一样,我们要在系统中根据项目的具体需求来实现IBasicHandler接口并将其注入到框架中。

用户管理器

  服务端如何知道用户上下线、以及每个在线用户的状态了?
  只要通过ESFramework.Server.UserManagement.IUserManager的相关事件和方法就能得到这些信息。我们可以从ESPlus.Rapid.IRapidServerEngine暴露的UserManager属性来获取IUserManager引用。IUserManager接口定义如下:

    public interface IUserManager        {                /// <summary>                /// 当前在线用户的数量。                 /// </summary>                int UserCount { get; }                         /// <summary>                /// 目标用户是否在线。                 /// </summary>                       bool IsUserOnLine(string userID);                         /// <summary>                /// 获取目标在线用户的基础信息。                /// </summary>                /// <param name="userID">目标用户的ID</param>               /// <returns>如果目标用户不在线,则返回null</returns>                UserData GetUserData(string userID);                                 /// <summary>                /// 获取在线用户的ID列表。                /// </summary>                        List<string> GetOnlineUserList();                         /// <summary>               /// 从目标用户集合中挑出在线用户的ID列表。                /// </summary>                        List<string> SelectOnlineUserFrom(IEnumerable<string> users);                         /// <summary>                /// 重登陆模式。                 /// </summary>                RelogonMode RelogonMode { get; set; }                                  /// <summary>                /// 获取当目标在线用户的信息。                 /// </summary>                /// <param name="userID">目标用户的ID</param>                /// <returns>如果目标用户不在线,则返回null</returns>                RichUserData GetRichUserData(string userID);                                   /// <summary>                /// 如果用户不在线,返回null                    /// </summary>                  UserAddress GetUserAddress(string userID);                                /// <summary>                /// 客户端连接被关闭时,将触发此事件。不要远程预定该事件。                 /// </summary>                event CbGeneric<UserData ,DisconnectedType> SomeOneDisconnected;                         /// <summary>                /// 当接收到新连接上的第一个消息时,将触发此事件。不要远程预定该事件。                 /// </summary>                event CbGeneric<UserData> SomeOneConnected;                         /// <summary>                /// 如果RelogonMode为ReplaceOld,并且当从另外一个新连接上收到一个同名ID用户的消息时将触发此事件。                 /// 注意,只有在该事件处理完毕后,才会真正关闭旧的连接并使用新的地址取代旧的地址。可以在该事件的处理函数中,将相关情况通知给旧连接的客户端。                 /// </summary>                event CbGeneric<UserData> SomeOneBeingPushedOut;                         /// <summary>                /// 如果RelogonMode为IgnoreNew,并且当从一个新连接上收到一个同名ID用户的消息时将触发此事件。                    /// 注意,只有在该事件处理完毕后,才会关闭新连接。可以在该事件的处理函数中,将相关情况通知给客户端。                       /// </summary>                event CbGeneric<string, UserAddress> NewConnectionIgnored;                        /// <summary>                /// 用户心跳超时。                 /// 只有在该事件处理完毕后,才关闭对应的连接,并将其从用户列表中删除。可以在该事件的处理函数中,将相关情况通知给客户端。                             /// </summary>                event CbGeneric<UserData> SomeOneTimeOuted;                                /// <summary>                /// 当在线用户数发生变化时,触发此事件。                 /// </summary>                event CbGeneric<int> UserCountChanged;        }

      RelogonMode属性用于设置重登录模式,关于重登陆模式的更多内容可以参见重登陆模式。

 

3.UserID的长度

  在ESFramework 4.0 进阶(01)-- 消息一文中我们介绍了ESPlus提供了默认的消息头实现,而Rapid引擎使用的就是ESPlus提供的基于二进制的消息头StreamMessageHeader,这个消息头的默认长度是36字节,允许的UserID最大长度为11字节。但是,如果你的系统中需要用到的UserID长度超过了11字节,该怎么办了?我们可以通过调用StreamMessageHeader的SetMaxLengthOfUserID静态方法来设定ESFramework允许的UserID的最大长度:

    /// <summary>    /// 设置UserID(包括GroupID)的最大长度(不能超过255)。注意,客户端与服务端要统一设置。    /// </summary>      public static void SetMaxLengthOfUserID(byte maxLen);
  注意,我们必须在Rapid引擎的Initialize方法执行之前调用SetMaxLengthOfUserID方法。而且,客户端和服务端必须采用相同的设置,否则,就一定会导致服务端和客户端通信出现异常。如果你的客户端是使用的Silverlight,那么使用ESFramework.SL时也是如此。
  • 服务端和桌面客户端请调用ESPlus.Core.StreamMessageHeader的SetMaxLengthOfUserID方法进行设置。
  • Silverlight的客户端请调用ESFramework.SL.StreamMessageHeader的SetMaxLengthOfUserID方法进行设置。

      在ESFramework内部,组ID(即上面提到的groupID)也采用与UserID相同的规则。
  还要提醒的是,在能满足项目需求的情况下,尽可能使UserID的最大长度短一点,这样可以使得消息头更加短小,从而避免浪费本不需要的带宽。尤其在高性能、巨大并发的应用中,这点就更关键了。

 

4.消息的最大长度

     Rapid引擎内部默认设置的消息的最大长度为1M(1024*1024),并且这个长度还包含了上述消息头的长度。如果您的应用需要发的单个信息的长度超过了1M,就会被ESFramework认为是恶意的消息,ESFramework会丢弃该消息并关闭对应的连接。

我们建议:在能同样满足项目的需求下,应该尽可能地使传送的消息小,这样不仅可以节省带宽,而且还有助于提升并发的性能。如果应用中确实需要信息的长度超过1M,那么可以通过Rapid引擎暴露的内部核心引擎接口来设置所允许的最大消息长度。

  • 服务端:RapidServerEngine暴露的TcpServerEngine内核引擎,可以设置其MaxMessageSize属性的值。
  • 客户端:RapidPassiveEngine暴露的TcpPassiveEngine内核引擎,也可以设置其MaxMessageSize属性的值。

   

      最后,大家可以查看最简单的那个demo的源码,并运行demo,来了解上述的状态改变通知及其它基础功能。谢谢。

      阅读 更多ESFramework开发手册系列文章。

-----------------------------------------------------------------------------------------------------------------------------------------------

下载免费版本的ESFramework 以及 demo源码

关于ESFramework的任何问题,欢迎联系我们:

电话:15807162718
Q Q:372841921
邮件:esframework@oraycn.com

 

原创粉丝点击