无论我们采用何种通信框架来构建我们的分布式系统,在服务端进行用户管理都是非常重要的一个环节。然而用户管理是否应该隶属于通信框架了?这个并不一定,通常来说,用户管理是与具体应用紧密相关的,应该是由应用解决的部分,因为不同的应用程序对用户管理的需求是不尽相同的。但是,如果我们对大多数应用中的用户管理任务进行分析,我们发现它们都会关注一些最基础的用户管理需求(如用户状态监控)。如果能在通信框架中内置一种简洁的、灵活的、可扩展的用户管理组件,定会为大多数应用程序提供非常多的方便。

ESFramework抽象了最基本的用户管理任务,定义了统一的用户管理的基础接口,并提供了默认的实现,它们能与前面介绍的网络通信引擎无缝地集成。对于许多不是特别复杂的分布式通信应用,ESFramework内置的用户管理功能几乎就可以满足要求。另外,ESFramework和ESPlus以及ESPlatform的许多扩展特性和功能也需要基于用户管理组件的抽象接口来展开。

一.用户管理器接口IUserManager

  用户管理器是用在服务端的,所以IUserManager接口的定义位于ESFramework.Server.UserManagement命名空间,IUserManager不仅可以用于基于TCP的服务端、也可以用于基于UDP的服务端。

我们首先看看其类图:

IUserManager继承自ICoreUserManager接口,ICoreUserManager是更基础的用户管理接口,其主要被用于ESPlatform平台,否则,其中定义的内容可以完全合并到IUserManager接口中。下面我把这两个接口的定义源码贴出来,里面较详细的注释说明可以让我少敲些字。


    public interface ICoreUserManager
    {
        /// <summary>
        /// 在线用户的数量。
        /// </summary>
        int UserCount { get; }         /// <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;
    }

    public interface IUserManager : ICoreUserManager
    {
        /// <summary>
        /// 用户管理器依赖该属性显示所有在线用户的状态信息。    
        /// </summary>
        IUserDisplayer UserDisplayer { set; }         /// <summary>
        /// 在线用户的心跳检测器。
        /// </summary>
        IHeartBeatChecker HeartBeatChecker { set; }          /// <summary>
        /// 重登陆模式。
        /// </summary>
        RelogonMode RelogonMode { get; set; }             /// <summary>
        /// 初始化用户管理器。
        /// </summary>
        void Initialize();         /// <summary>
        /// 清除所有的在线用户。
        /// </summary>
        void Clear();  
          
        /// <summary>
        /// 激活心跳。
        /// </summary>        
        void ActivateUser(string userID);         /// <summary>
        /// 目标用户是否在线。
        /// </summary>       
        bool IsUserOnLine(string userID);         /// <summary>
        /// 从在线列表中移除目标用户(依据用户的地址)。
        /// </summary>        
        void UnregisterUser(IUserAddress address);         /// <summary>
        /// 从在线列表中移除目标用户。
        /// </summary>
        void UnregisterUser(string userID);               /// <summary>
        /// 根据在线用户的地址获取用户ID。
        /// </summary>        
        string GetUserID(IUserAddress address);         /// <summary>
        /// GetTimeLogon 获取目标在线用户的登录时间。
        /// </summary>       
        DateTime GetTimeLogon(string userID);         /// <summary>
        /// 用于统计发送给用户的消息。如果用户还未注册,则忽略。
        /// </summary>        
        void AfterSentMessageToUser(string userID, IUserAddress address, IMessage msg);         /// <summary>
        /// 如果用户不在线,返回null    
        /// </summary>  
        IUserAddress GetUserAddress(string userID);         /// <summary>
        /// 当用户已经注册过,如果RelogonMode为IgnoreNew,则不会更改其address,而是直接返回;如果RelogonMode为ReplaceOld,则使用新的连接取代旧的连接。
        /// </summary>       
        void RegisterUser(string userID, IUserAddress address); 
        
        /// <summary>
        /// 旧连接被挤掉。如果RelogonMode为ReplaceOld,并且当从另外一个新连接上收到一个同名ID用户的消息时将触发此事件。
        /// </summary>
        event CbGeneric<UserData> SomeOneBeingPushedOut;         /// <summary>
        /// 新连接被忽略。如果RelogonMode为IgnoreNew,并且当从一个新连接上收到一个同名ID用户的消息时将触发此事件。   
        /// </summary>
        event CbGeneric<string ,IUserAddress> NewConnectionIgnored;         /// <summary>
        /// 用户超时。只有在该事件处理完毕后,才将其从用户列表中删除。
        /// 但是,用户对应的TCP连接可能并没有被释放 -- 所以,在该事件处理函数中最好主动关闭TCP连接(将触发SomeOneDisconnected事件)。       
        /// </summary>
        event CbGeneric<UserData> SomeOneTimeOuted;         /// <summary>
        /// 当在线用户数发生变化时,触发此事件。
        /// </summary>
        event CbGeneric<int> UserCountChanged;
    }

(1)默认情况下,IUserManager会从客户端发过来的第一条消息中取出消息头的UserID属性的值,并将其与对应的用户地址IUserAddress对应起来,并触发SomeOneConnected事件。有一点我们是要特别注意,那就是同一个客户端每次给服务器发消息时,要保证每个消息的Header中UserID是一致的。而用户管理器只会认定第一个消息的Header中的UserID的值。

(2)在用户管理器中,UserAddress与UserID是一一对应的,一个UserAddress实例只能对应一个UserID,同样的,一个UserID最多存在一个UserAddress对象。我们可以通过GetUserID方法通过UserAddress获取对应的UserID,也可以通过GetUserData方法根据UserID得到对应的UserAddress对象。UserAddress对象中包括了客户端与服务器进行通信的IP地址和Port信息。

(3)如果从不同的连接上接收到相同UserID的信息,IUserManager采取的策略取决于RelogonMode属性的设置,其有可能触发SomeOneBeingPushedOut事件(RelogonMode为ReplaceOld)、也有可能触发NewConnectionIgnored事件(RelogonMode为IgnoreNew)。关于重登陆方面的更多信息,可以参见ESFramework 4.0 快速上手 -- 重登陆模式

(4)在处理SomeOneBeingPushedOut事件时要注意:只有在该事件处理完毕后,IUserManager才会真正使用新的地址取代旧的地址。所以,我们必须在该事件的处理函数中,关闭旧的连接(TCP)或Session(UDP)以释放资源(将触发SomeOneDisconnected事件)。但是在关闭之前,可以将相关情况通知给旧连接/Session的客户端。

(5)在处理NewConnectionIgnored事件时也要注意:我们必须在该事件的处理函数中,关闭新的连接或Session,同样的,在关闭之前,可以将相关情况通知给该连接/Session的客户端。

(6)当通信引擎检测到底层连接断开时(针对TCP),IUserManager会触发SomeOneDisconnected事件。

(7)IUserManager借助于IHeartBeatChecker对在线用于进行心跳检测,如果用户因为心跳超时,则会触发SomeOneTimeOuted事件。关于ESFramework中的TCP掉线与心跳机制的更多信息,可以参见ESFramework 4.0 快速上手 -- 玩的就是“心跳”

二.用户状态跟踪及显示

(1)当用户上线时,IUserManager会记录其上线时间和地址UserAddress。

(2)当服务端每次向客户端发送数据之后,IUserManager会通过AfterSentMessageToUser方法来进行记录,以此可以统计每个用户请求服务的次数和下载的数据量。

(3)我们可以通过IUserManager接口的GetUserData方法获取在线用户的实时状态。GetUserData返回UserData对象,从该对象的类图可以看出其包含了哪些状态信息:

(4)IUserManager接口还可以注入一个IUserDisplayer属性,通过IUserDisplayer可以在UI上显示每个用户的实时状态。


    public interface IUserDisplayer 
    { 
        /// <summary>
        /// 清除所有。通常是通信引擎停止时被调用。
        /// </summary>
        void ClearAll() ;         /// <summary>
        /// 当用户状态更新时被调用。
        /// </summary>
        /// <param name="userID">用户ID</param>
        /// <param name="justServiceType">刚刚发送给用户的消息类型</param>
        /// <param name="totalDataLen">该用户下载的总的数据量</param>
        /// <param name="userAddress">用户的地址</param>
        /// <param name="totalReqCount">总的请求次数</param>
        void SetOrUpdateUserItem(string userID, int justServiceType, long totalDataLen, string userAddress, long totalReqCount);                   /// <summary>
        /// 移除用户。通常在用户下线时被调用。
        /// </summary>       
        void RemoveUser(string userID ,string cause);
    }  

IUserManager借助于IUserDisplayer来展现每个在线用户的状态,我们可以实现该接口,以我们想要的方式显示这些状态数据。

如果没有特别需求,可以直接使用ESPlus提供的ESPlus.Widgets.UserDisplayer控件,它显示的效果如下所示:

一定要提醒读者的是,如果服务器处理的是巨大并发量的任务,就不要使用任何形式的UserDisplayer控件,甚至,服务端最好都不要有UI,而是以一个service的形式运行。因为在这种巨大并发量的系统中,在线用户数量巨大(数万计),而且状态更新频繁,会导致大量的CPU时间浪费在UI的更新上。经验之谈,谨记之。

三.与通信引擎集成

ESFramework通过ESFramework.Server.UserManagement.UserManagerBridge将用户管理器IUserManager与服务端引擎IServerEngine桥接起来:

  在UserManagerBridge的Initialize方法中,它会将IServerEngine的相关事件(如MessageReceived事件、MessageSent事件,对于TCP服务端引擎还有SomeOneDisconnect事件)传递给IUserManager的对应方法去处理,这样用户管理器就被驱动起来了。

最后,我们实例化一个用户管理器并就将其与服务端引擎集成起来作为示范:


    StreamTcpEngine streamTcpEngine = ......;

    IHeartBeatChecker heartBeatChecker = new ESBasic.Threading.Application.HeartBeatChecker(1, 30);//心跳超时设为30秒
    heartBeatChecker.Initialize();
    IUserManager userManager = new UserManager();
    userManager.HeartBeatChecker = heartBeatChecker;
    userManager.RelogonMode = RelogonMode.ReplaceOld;//设置重登陆模式
    userManager.Initialize();     UserManagerBridge userManagerBridge = new UserManagerBridge();
    userManagerBridge.UserManager = userManager;
    userManagerBridge.ServerEngine = streamTcpEngine;
    userManagerBridge.Initialize();

  关于如何实例化一个TCP服务端引擎,可以参见ESFramework 4.0 进阶(04)-- 驱动力:通信引擎(下)一文中的代码示例。

在线用户管理--ESFramework 4.0 进阶(05)的更多相关文章

  1. 好友与组--ESFramework 4.0 进阶(11)

    大部分分布式通信系统中,都会涉及到客户端之间相互通信.以及需要将客户端进行分组的功能,或者是类似这方面的需求.ESFramework对这一常见的任务内置了强大的支持,包括从客户端到服务端.一直到Pla ...

  2. 消息同步调用-- ESFramework 4.0 进阶(07)

    分布式系统的构建一般有两种模式,一是基于消息(如Tcp,http等),一是基于方法调用(如RPC.WebService.Remoting).深入想一想,它们其实是一回事.如果你了解过.NET的Prox ...

  3. 挂接P2P通道-- ESFramework 4.0 进阶(08)

    最新版本的ESFramework/ESPlus提供了基于TCP和UDP的P2P通道,而无论我们是使用基于TCP的P2P通道,还是使用基于UDP的P2P通道,ESPlus保证所有的P2P通信都是可靠的. ...

  4. 正规消息发送器-- ESFramework 4.0 进阶(06)

    在ESFramework 4.0 进阶(04)-- 驱动力:通信引擎(下)一文末尾我们已经将通信引擎以及整个消息骨架流程组装起来了,只要通信引擎一接收到消息,框架就会按照规定的流程进行运转.到这里,自 ...

  5. 驱动力—— 通信引擎(上)—— ESFramework 4.0 进阶(03)

    在ESFramework 4.0 进阶(02)-- 核心:消息处理的骨架流程一文中我们详细介绍了ESFramework中消息处理的骨架流程,并且我们已经知道,ESFramework中的所有通信引擎使用 ...

  6. 垂直分割群集模型与多通道引擎 -- ESFramework 4.0 进阶(10)

    在ESFramework 4.0 进阶(09)-- ESPlatform 支持的三种群集模型一文中,我们介绍了ESPlatform支持的三种群集模型 -- 垂直分割模型.水平分割模型.交叉模型.我们看 ...

  7. ESFramework 4.0 进阶(04)-- 驱动力:通信引擎(下)

    在ESFramework 4.0 进阶(03)-- 驱动力:通信引擎(上)一文中,我们对ESFramework提供的每一个通信引擎的接口都做了详细了说明,这篇文章我们将继续探讨这些接口的实现类 -- ...

  8. 核心梳理——消息处理的骨架流程——ESFramework 4.0 进阶(02)

    在ESFramework 4.0 概述一文中,我们提到ESFramework.dll作为通信框架的核心,定义了消息处理的骨架流程,本文我们来详细剖析这个流程以及该骨架中所涉及的各个组件.ESFrame ...

  9. Sangfor_AC用户不在线但在“在线用户管理”里有显示

    现象:用户实际不在线,但是在“在线用户管理”里有看到,而且在线时间很长. 分析:用户通过IP上线以后,只要IP地址在线或者下线时间不超过“无流量自动注销的时间”,那么就会显示到“在线用户管理”里. 而 ...

随机推荐

  1. ECMAScript6之Array类型的扩展

    数组的扩展 Array.of() 将一组值转换成数组 Array.of(1,2,3,4,5); //[1,2,3,4,5] Array.from() 可将类似数组的对象或者可便利的对象转换成数组,比如 ...

  2. The APR based Apache Tomcat Native library tomcat启动错误

    The APR based Apache Tomcat Native library which allows optimal performance in production environmen ...

  3. ReactJS 生命周期、数据流与事件

    React是一个JavaScript库文件,使用它的目的在于能够解决构建大的应用和数据的实时变更.该设计使用JSX允许你在构建标签结构时充分利用JavaScript的强大能力,而不必在笨拙的模板语言上 ...

  4. 7、Objective-C中的各种遍历(迭代)方式

    一.使用for循环 要遍历字典.数组或者是集合,for循环是最简单也用的比较多的方法,示例如下: //普通的for循环遍历 -(void)iteratorWithFor { //////////处理数 ...

  5. 项目有叉号, 但是没有代码错误的时候, 是JDK版本的问题

    windows---proferences---java--compiler进入项目--properties---java Compiler进入项目--properties---Myeclipse-- ...

  6. x264宏块及子块划分方式

    1 宏块划分方式 一副图像(帧,非场图像,x264支持宏块级场编码,这里以帧图像为例说明)按从左到右.从上到下16x16的方式划分宏块,对于图像宽度不是16的倍数的情况,会扩展至16的倍数,然后通过s ...

  7. MSMQ小Demo

    Demo基于http://www.cnblogs.com/zhili/p/MSMQ.html Server代码: using System.Messaging; using System.Text; ...

  8. CSS实现三角形图标的原理《转载》

    网页中经常有一种三角形的图标,鼠标点一下会弹出一个下拉菜单之类的(之前淘宝也有,不过现在改版好像没有了) 之前以为是个png图标背景,后来在bootstrap中看到有一个图标样式叫做caret的用来实 ...

  9. Output\TEST.sct(7): error: L6236E: No section matches selector - no section to be FIRST/LAST.

    点击错误信息,跳转到了一个.sct文件:*.o (RESET, +First) 按照如下操作,也不能解决问题.对比别的工程,也没找出问题. "操作是: Options for Target ...

  10. LEK-Introduction-Installation-Usage-new

    LEK is a set of tools which can take data from any source and search, analyze, and visualize it in r ...