当我们的应用中客户端与AS之间是通过Tcp进行通信的时候,通常,应用也要求管理所有在线的用户。这种管理至少包含以下几点:
(1) 当用户上线时,记录上线时间
(2) 当用户请求服务时,记录请求服务的时间、服务的类型、本次服务下载的数据量
(3) 当用户下线时,记录下线时间。并把本次用户登录、请求服务过程中的所有信息持久化保存(如记录到数据库)
在ESFramework中,实现这种管理的是ITcpUserManager组件,通常,该组件由AS使用(因为在AS、FS、IRAS中只有AS是必须要求提供用户管理功能的)。除了上述功能外,ITcpUserManager组件还提供:
(1) 控制UI上用户实时状态的显示
(2) 对用户意外掉线进行定时检查
(3) 提供用户是否在线的查询
(4) 提供通过用户ID获取Tcp连接的查询
为什么ITcpUserManager组件需要提供这些功能?为什么有些功能不在其他组件中提供、而非在ITcpUserManager组件之中不可?这些问题都需要你进行深入的思考,就像我们当初所做的那样。ITcpUserManager组件一开始并不是这样的设计,在经过几次重构改善后才是这个样子,现在这个设计已经稳定下来了。在思考的过程中,你需要记住一点:ITcpUserManager组件是用户状态最全面、最实时发布的地方。
ITcpUserManager接口定义如下:
1
public
interface
ITcpUserManager
2
{
3
void
Start() ;
4
void
Stop() ;
5
6
void
DisposeOneUser(
string
userID ,DisconnectedCause cause) ;
7
void
DisposeOneConnection(
int
connectID ,DisconnectedCause cause) ;
8
void
ServiceCommited(
int
connectID ,
string
userID ,
int
serviceKey ,
int
dataCount) ;
9
void
ActivateUser(
string
userID) ;
10
11
bool
IsUserOnLine(
string
userID) ;
12
int
GetUserConnectID(
string
userID) ;
//
如果不在线,返回-1
13
string
[] GetOnlineUserList() ;
//
IList中是在线的userID
14
15
event
CbForTcpUserDisconn SomeOneDisconnected ;
16
event
CbSimpleStr SomeOneConnected ;
//
UserID
17
event
CbSimple Restarted ;
18
19
ITcpUserDisplayer TcpUserDisplayer{
set
;}
//
控制UI
20
IUserTaskReporter UserTaskReporter{
set
;}
//
持久化服务记录清单
21
int
OnLineCheckSpan{
get
;
set
;}
//
OnLineCheckSpan单位为分钟,如果不使用定时检查,则onLineCheckSpan为-1
22
23
}
那么,ITcpUserManager组件从哪里获取用户的这些实时信息了?
(1) 网络插件的ServiceCommited事件、网络插件的SomeOneDisConnected事件
(2) 定时检查器的掉线事件
(3) 基本请求处理者的RequestWithoutRespondArrived事件=》激活定时检查器中的某个用户
(4) Logout请求
上述的几个信息来源决定了ITcpUserManager组件与其他几个组件的依赖关系。综合上面所有的,下面是ITcpUserManager组件与其他主要组件的依赖关系图:
这些依赖关系的组装是由TcpUserManagerBridge类完成的,看看它的代码就知道怎么回事了:
TcpUserManagerBridge
1 public class TcpUserManagerBridge
2 {
3 private IBasicRequestDealer basicRequestDealer = null ;
4 private ITcpUserManager tcpUserManager = null ;
5 private ITcp theTcp = null ;
6 private IContractHelper contractHelper = null ;
7 private bool directSeviceCommitEnabled = false ;
8
9
10 public TcpUserManagerBridge()
11 {
12
13 }
14
15 public void Initialize()
16 {
17 this.theTcp.ServiceCommitted += new CallBackRespond(theTcp_ServiceCommitted);
18 this.theTcp.SomeOneDisConnected += new CallBackDisconnect(theTcp_SomeOneDisConnected);
19 this.basicRequestDealer.RequestWithoutRespondArrived += new CbSimpleStr(basicRequestDealer_RequestWithoutRespondArrived);
20 this.basicRequestDealer.SomeOneLogout += new CbSimpleStr(basicRequestDealer_SomeOneLogout);
21 this.theTcp.ServiceDirectCommitted += new CallBackRespond(theTcp_ServiceDirectCommitted);
22 }
23
24 #region property
25 public ITcpUserManager TcpUserManager
26 {
27 set
28 {
29 this.tcpUserManager = value ;
30 }
31 }
32
33 public ITcp Tcp
34 {
35 set
36 {
37 this.theTcp = value ;
38 }
39 }
40
41 public IBasicRequestDealer BasicRequestDealer
42 {
43 set
44 {
45 this.basicRequestDealer = value ;
46 }
47 }
48
49 public IContractHelper ContractHelper
50 {
51 set
52 {
53 this.contractHelper = value ;
54 }
55 }
56
57 public bool DirectSeviceCommitEnabled
58 {
59 set
60 {
61 this.directSeviceCommitEnabled = value ;
62 }
63 }
64 #endregion
65
66 private void theTcp_SomeOneDisConnected(int ConnectID, DisconnectedCause cause)
67 {
68 this.tcpUserManager.DisposeOneConnection(ConnectID ,cause) ;
69 }
70
71 private void basicRequestDealer_RequestWithoutRespondArrived(string userID)
72 {
73 this.tcpUserManager.ActivateUser(userID) ;
74 }
75
76 private void basicRequestDealer_SomeOneLogout(string userID)
77 {
78 this.tcpUserManager.DisposeOneUser(userID ,DisconnectedCause.Logoff) ;
79 }
80
81 private void theTcp_ServiceCommitted(int connectID, NetMessage msg)
82 {
83 this.tcpUserManager.ServiceCommited(connectID ,msg.Header.UserID ,msg.Header.ServiceKey ,msg.Header.MessageBodyLength + this.contractHelper.MessageHeaderLength) ;
84 }
85
86 private void theTcp_ServiceDirectCommitted(int connectID, NetMessage msg)
87 {
88 if(this.directSeviceCommitEnabled)
89 {
90 this.tcpUserManager.ServiceCommited(connectID ,msg.Header.UserID ,msg.Header.ServiceKey ,msg.Header.MessageBodyLength + this.contractHelper.MessageHeaderLength) ;
91 }
92
93 }
94 }
TcpUserManagerBridge相当与一种桥接模式(可从这里了解更多),使得组件与组件之间的耦合关系达到最小。
在ESFramework内部,很多地方需要使用到ITcpUserManager组件,比如P2PMessageDealer组件中、ToLocalClientSender组件中等等,它们主要通过ITcpUserManager组件获取某用户的Tcp连接、或查看某用户是否在线。
最后提一下ITcpUserManager用到的两个组件:IUserOnLineChecker和IUserTaskReporter。
IUserOnLineChecker用于实现前面文章中谈到的“定时check消息”机制,当某个用户在指定的时间内没有任何消息发给服务器时,IUserOnLineChecker触发SomeConnectionTimeOuted事件通知该用户已经掉线。IUserOnLineChecker接口定义非常简单:
1
public
interface
IUserOnLineChecker
2
{
3
void
Start() ;
4
void
Stop() ;
5
6
void
RegisterOrActivateUser(
string
userID) ;
7
void
UnregisterUser(
string
userID) ;
//
被外界调用UnregisterUser时,不触发CheckSomeOneDisConnected事件
8
9
int
CheckSpan{
get
;
set
;}
//
Minutes
10
11
event
CbSimpleStr SomeConnectionTimeOuted ;
//
仅是当定时检查出用户掉线时才触发
12
}
前面我们提到,当用户下线(掉线)时,需要把该用户本次登录的服务记录永久存储,这个功能是由IUserTaskReporter来完成的。
1
public
interface
IUserTaskReporter
2
{
3
void
RecordTaskList(ITaskMainRecord mainRecord) ;
4
ITaskMainRecord CreateRudeTaskMainRecord() ;
5
ITaskDetailRecord CreateRudeTaskDetailRecord() ;
6
}
7
8
public
interface
ITaskMainRecord
9
{
10
string
UserID{
get
;
set
;}
11
int
TotalDataCount{
get
;
set
;}
12
DateTime TimeLogon{
get
;
set
;}
13
DateTime TimeLogout{
get
;
set
;}
14
int
RequestCount{
get
;
set
;}
15
16
ArrayList Details{
get
;
set
;}
//
details中为ITaskDetailRecord
17
}
18
19
public
interface
ITaskDetailRecord
20
{
21
int
ServiceKey{
get
;
set
;}
22
int
DataCount{
get
;
set
;}
23
DateTime RequestTime{
get
;
set
;}
24
}
持久化的具体介质由你的应用决定。如果你使用数据库来存储,则可以将ITaskMainRecord和ITaskDetailRecord在数据库中设计成主从表关系。
上一篇文章:ESFramework介绍之(17)―― 支持漫游用户和跨区域功能请求
转到 :ESFramework 可复用的通信框架(序)