在线用户管理--ESFramework 4.0 进阶(05)
無論我們采用何種通信框架來構建我們的分布式系統,在服務端進行用戶管理都是非常重要的一個環節。然而用戶管理是否應該隸屬于通信框架了?這個并不一定,通常來說,用戶管理是與具體應用緊密相關的,應該是由應用解決的部分,因為不同的應用程序對用戶管理的需求是不盡相同的。但是,如果我們對大多數應用中的用戶管理任務進行分析,我們發現它們都會關注一些最基礎的用戶管理需求(如用戶狀態監控)。如果能在通信框架中內置一種簡潔的、靈活的、可擴展的用戶管理組件,定會為大多數應用程序提供非常多的方便。
??? ?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)-- 驅動力:通信引擎(下)一文中的代碼示例。
轉載于:https://www.cnblogs.com/sylone/p/6096943.html
總結
以上是生活随笔為你收集整理的在线用户管理--ESFramework 4.0 进阶(05)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: flex属性值----弹性盒子布局
- 下一篇: 把CentOS启动进度条替换为详细信息