我阅读过几个论坛的在线用户统计代码,发现其中有两个问题,一个是需要借助数据库,另外一个是“锁”的粒度比较强!在线用户统计并不要求十分的精确(在这篇文章里,我不会讨论如何侦测到浏览器的关闭动作,而是讨论如何提高代码性能),那么借助数据库来完成这样的功能就显得很夸张!更重要的是对数据库进行读写操作(I/O操作),是要消耗性能的,而且还要在数据表里产生一条记录。为了一个不精确的功能需求消耗如此多的资源,的确不划算!另外一个办法是直接使用DataSet和ASP.NET缓存的方式来做统计,类似这样的代码我看过几个,自己也写过一个。但这样做也存在很大问题,最严重的地方还是“锁”的问题。在更新DataSet时需要通过lock关键字来将其锁定,但如果用户数量很大时,DataSet被加锁的次数过于频繁,所造成的坏结果千奇百怪。所以我不得不寻找一种更为有效的方法……新的方法是通过命令模式更新在线用户列表
>>获取该文章的源码
我阅读过几个论坛的在线用户统计代码,发现其中有两个问题,一个是需要借助数据库,另外一个是“锁”的粒度比较强!在线用户统计并不要求十分的精确(在这篇文章里,我不会讨论如何侦测到浏览器的关闭动作,而是讨论如何提高代码性能),那么借助数据库来完成这样的功能就显得很夸张!更重要的是对数据库进行读写操作(I/O操作),是要消耗性能的,而且还要在数据表里产生一条记录。为了一个不精确的功能需求消耗如此多的资源,的确不划算!另外一个办法是直接使用DataSet和ASP.NET缓存的方式来做统计,类似这样的代码我看过几个,自己也写过一个。但这样做也存在很大问题,最严重的地方还是“锁”的问题。在更新DataSet时需要通过lock关键字来将其锁定,但如果用户数量很大时,DataSet被加锁的次数过于频繁,所造成的坏结果千奇百怪。所以我不得不寻找一种更为有效的方法。
在进行讨论之前,有必要先做一个介绍。在线用户信息应该包括:
- SessionID
- 用户名称
- 最后活动时间
- 最后请求地址(Url地址)
还可以包括IP地址或其他更详细的信息。这是在线用户信息的数据结构,在线用户统计的算法是:
1. 将在线用户信息插入到集合,如果集合中已经存在相同用户名称的数据项,则更新该数据项;
2. 根据最后活动时间倒排序;
排序步骤虽然可以在列表显示在线用户信息的时候再做,但是那样会花费一点时间,不如在插入数据项以后马上排序,需要显示的时候直接显示。OnlineUser数据结构代码如下:
1
using System;
2
using System.Collections.Generic;
3![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
4
namespace Net.AfritXia.Web.OnlineStat
5![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
6![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
7
/// 在线用户类
8
/// </summary>
9
public class OnlineUser
10![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
11
// 用户 ID
12
private int m_uniqueID;
13
// 名称
14
private string m_userName;
15
// 最后活动时间
16
private DateTime m_lastActiveTime;
17
// 最后请求地址
18
private string m_lastRequestURL;
19
// SessionID
20
private string m_sessionID;
21
// IP 地址
22
private string m_clientIP;
23![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
24![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
类构造器#region 类构造器
25![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
26
/// 类默认构造器
27
/// </summary>
28
public OnlineUser()
29![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
30
}
31![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
32![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
33
/// 类参数构造器
34
/// </summary>
35
/// <param name="uniqueID">用户 ID</param>
36
/// <param name="userName">用户名称</param>
37
public OnlineUser(int uniqueID, string userName)
38![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
39
this.UniqueID = uniqueID;
40
this.UserName = userName;
41
}
42
#endregion
43![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
44![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
45
/// 设置或获取用户 ID
46
/// </summary>
47
public int UniqueID
48![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
49
set
50![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
51
this.m_uniqueID = value;
52
}
53![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
54
get
55![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
56
return this.m_uniqueID;
57
}
58
}
59![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
60![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
61
/// 设置或获取用户昵称
62
/// </summary>
63
public string UserName
64![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
65
set
66![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
67
this.m_userName = value;
68
}
69![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
70
get
71![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
72
return this.m_userName;
73
}
74
}
75![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
76![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
77
/// 最后活动时间
78
/// </summary>
79
public DateTime ActiveTime
80![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
81
set
82![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
83
this.m_lastActiveTime = value;
84
}
85![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
86
get
87![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
88
return this.m_lastActiveTime;
89
}
90
}
91![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
92![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
93
/// 最后请求地址
94
/// </summary>
95
public string RequestURL
96![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
97
set
98![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
99
this.m_lastRequestURL = value;
100
}
101![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
102
get
103![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
104
return this.m_lastRequestURL;
105
}
106
}
107![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
108![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
109
/// 设置或获取 SessionID
110
/// </summary>
111
public string SessionID
112![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
113
set
114![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
115
this.m_sessionID = value;
116
}
117![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
118
get
119![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
120
return this.m_sessionID;
121
}
122
}
123![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
124![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
125
/// 设置或获取 IP 地址
126
/// </summary>
127
public string ClientIP
128![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
129
set
130![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
131
this.m_clientIP = value;
132
}
133![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
134
get
135![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
136
return this.m_clientIP;
137
}
138
}
139
}
140
}
对于在线用户列表数据集,我们只用一个List<OnlineUser>对象来表示就可以了,不过我现在是把它封装在OnlineUserRecorder类里。代码如下:
1
using System;
2
using System.Collections.Generic;
3
using System.Threading;
4![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
5
namespace Net.AfritXia.Web.OnlineStat
6![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
7![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
8
/// 在线用户记录器
9
/// </summary>
10
public class OnlineUserRecorder
11![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
12
// 在线用户列表
13
private List<OnlineUser> m_onlineUserList = new List<OnlineUser>();
14![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
15![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
16
/// 保存在线用户
17
/// </summary>
18
/// <param name="onlineUser">在线用户信息</param>
19
public void Persist(OnlineUser onlineUser)
20![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
21
if (onlineUser == null)
22
return;
23![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
24
lock (typeof(OnlineUserRecorder))
25![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
26
// 添加在线用户到集合
27
this.m_onlineUserList.Add(onlineUser);
28
// 按最后活动时间排序
29
this.m_onlineUserList.Sort(CompareByActiveTime);
30
}
31
}
32![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
33![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
34
/// 比较两个用户的活动时间
35
/// </summary>
36
/// <param name="x"></param>
37
/// <param name="y"></param>
38
/// <returns></returns>
39
private static int CompareByActiveTime(OnlineUser x, OnlineUser y)
40![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
41
if (x == null)
42
throw new NullReferenceException("X 值为空 ( X Is Null )");
43![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
44
if (y == null)
45
throw new NullReferenceException("Y 值为空 ( Y Is Null )");
46![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
47
if (x.LastActiveTime > y.LastActiveTime)
48
return -1;
49![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
50
if (x.LastActiveTime < y.LastActiveTime)
51
return +1;
52![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
53
return 0;
54
}
55
}
56
}
先不要管OnlineUserReader的代码是否正确,这还远远不是最终的代码。注意排序使用的是简化版的策略模式,这个并不主要。关键问题是在于lock代码段!代码执行到lock关键字时,需要判断锁定状态,如果正处于锁定状态,那么就会在此等待,直到被锁定的资源释放掉。想象一下,如果有两个用户先后请求一个页面,这个页面要需要执行这部分代码,那么就会有一个用户的请求先被处理,而另外一个用户只能原地等待。而恰好此时又来第三个用户,他也请求了这个页面,那么他也得等一会儿……第四个用户来了、第五个用户也来了……这样,用户就排起了长队。呵呵,等到第一个用户释放了被锁定的资源以后会发生什么情况呢?第二、三、四、五这几个用户会争夺资源!谁想抢到了,谁就先执行。也就是说第二个请求网页的用户,可能要等到最后才被执行。假如,第一个用户在执行代码的时候发生异常不能再继续执行,他也没有释放被锁定的资源,那么其他用户将无限期的等待下去……这就是死锁。
比如你去一个裁缝店,叫那里的小裁缝给你做套西服。你先选定一块布料,然后小裁缝会用皮尺量出你的身高、腰围、臂长、腿长等数据。再然后呢?小裁缝马上扯下一块布裁裁剪剪,开启缝纫机来个现场制作对么?如果这时候又来了一个顾客怎么办?这位新来的顾客就一直等着?等到你要的西服都做好了,才去招呼这位新来的顾客吗?也许这位新来的顾客等一会儿就摔门走人了。对于这个裁缝店算是丢掉了一笔买卖。但事实并不是这样子的!小裁缝是将你的身高、腰围等信息记录在一个收据单上,你选择什么样的布料也记录在这单子上,最后会告诉你几天以后来取就可以了。如果这个时候又来了一个顾客,小裁缝也一样记录这位新顾客的身高腰围等信息,并告诉他几天以后来取……
这就是小裁缝的智慧,即免除了顾客的等待时间,又为自己争取到了顾客量。你只需要选定一块布料,并且把自己的身高、腰围等信息留下就可以了。至于这个小裁缝是什么时候开工,手工过程是什么样的,你无需知道了。你只会记得到日子取回自己的西服。这就是命令模式!
命令模式的特点是:
- 命令的发出者和命令的执行者可能不是同一个人(不是同一个类或代码段,甚至不是在同一台服务器上);
- 命令的发出者发出命令给执行者以后会立即返回,或者说发出命令的时间和执行命令的时间可能会有很大间隔,是不同步的;
- 命令的发出者不知道也不想知道执行者的执行过程;
你就是命令的发出者,小裁缝就是命令的执行者,那张记录你身高、腰围等信息的收据单,就是命令对象!
对于统计在线用户,我们事将用户信息翻译成命令对象并记录到一个队列中。并不马上更新在下用户数据集合,而是延迟一段时间在更新。将用户更新信息积攒起来,等到一定时间后批量处理。这样就免除了对在线用户数据集的频繁加锁!更具体的说明如下:
1. 创建两个命令队列,cmdQueueA和cmdQueueX。cmdQueueA专门负责接收并存储新的命令对象;cmdQueueX专门负责存储即将执行的命令对象;
2. 在一定时间间隔之后,交换两个命令队列!系统(在下图中用红色曲线表示)将根据cmdQueueX中的命令对象,更新在线用户数据集合onlineUserList;在更新onlineUserList的时候需要加锁,所以使用两个命令队列可以保证命令的接收效率,也避免了对同一队列同时进行入出队操作;
最终代码,首先是OnlineUserRecorder类,该类属于“控制器”:
1
using System;
2
using System.Collections.Generic;
3
using System.Threading;
4![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
5
namespace Net.AfritXia.Web.OnlineStat
6![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
7![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
8
/// 在线用户记录器
9
/// </summary>
10
public class OnlineUserRecorder
11![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
12
// 在线用户数据库
13
private OnlineUserDB m_db = null;
14
// 命令队列A, 用于接收命令
15
private Queue<OnlineUserCmdBase> m_cmdQueueA = null;
16
// 命令队列X, 用于执行命令
17
private Queue<OnlineUserCmdBase> m_cmdQueueX = null;
18
// 繁忙标志
19
private bool m_isBusy = false;
20
// 上次统计时间
21
private DateTime m_lastStatisticTime = new DateTime(0);
22
// 用户超时分钟数
23
private int m_userTimeOutMinute = 20;
24
// 统计时间间隔
25
private int m_statisticEventInterval = 60;
26
// 命令队列长度
27
private int m_cmdQueueLength = 256;
28![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
29![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
类构造器#region 类构造器
30![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
31
/// 类默认构造器
32
/// </summary>
33
internal OnlineUserRecorder()
34![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
35
this.m_db = new OnlineUserDB();
36![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
37
// 初始化命令队列
38
this.m_cmdQueueA = new Queue<OnlineUserCmdBase>();
39
this.m_cmdQueueX = new Queue<OnlineUserCmdBase>();
40
}
41
#endregion
42![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
43![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
44
/// 设置或获取用户超时分钟数
45
/// </summary>
46
internal int UserTimeOutMinute
47![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
48
set
49![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
50
this.m_userTimeOutMinute = value;
51
}
52![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
53
get
54![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
55
return this.m_userTimeOutMinute;
56
}
57
}
58![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
59![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
60
/// 设置或获取统计时间间隔(单位秒)
61
/// </summary>
62
internal int StatisticEventInterval
63![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
64
set
65![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
66
this.m_statisticEventInterval = value;
67
}
68![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
69
get
70![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
71
return this.m_statisticEventInterval;
72
}
73
}
74![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
75![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
76
/// 设置或获取命令队列长度
77
/// </summary>
78
public int CmdQueueLength
79![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
80
set
81![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
82
this.m_cmdQueueLength = value;
83
}
84![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
85
get
86![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
87
return this.m_cmdQueueLength;
88
}
89
}
90![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
91![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
92
/// 保存在线用户信息
93
/// </summary>
94
/// <param name="onlineUser"></param>
95
public void Persist(OnlineUser onlineUser)
96![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
97
// 创建删除命令
98
OnlineUserDeleteCmd delCmd = new OnlineUserDeleteCmd(this.m_db, onlineUser);
99
// 创建插入命令
100
OnlineUserInsertCmd insCmd = new OnlineUserInsertCmd(this.m_db, onlineUser);
101![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
102
if (this.m_cmdQueueA.Count > this.CmdQueueLength)
103
return;
104![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
105
// 将命令添加到队列
106
this.m_cmdQueueA.Enqueue(delCmd);
107
this.m_cmdQueueA.Enqueue(insCmd);
108![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
109
// 处理命令队列
110
this.BeginProcessCmdQueue();
111
}
112![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
113![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
114
/// 删除在线用户信息
115
/// </summary>
116
/// <param name="onlineUser"></param>
117
public void Delete(OnlineUser onlineUser)
118![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
119
// 创建删除命令
120
OnlineUserDeleteCmd delCmd = new OnlineUserDeleteCmd(this.m_db, onlineUser);
121![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
122
// 将命令添加到队列
123
this.m_cmdQueueA.Enqueue(delCmd);
124![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
125
// 处理命令队列
126
this.BeginProcessCmdQueue();
127
}
128![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
129![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
130
/// 获取在线用户列表
131
/// </summary>
132
/// <returns></returns>
133
public IList<OnlineUser> GetUserList()
134![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
135
return this.m_db.Select();
136
}
137![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
138![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
139
/// 获取在线用户数量
140
/// </summary>
141
/// <returns></returns>
142
public int GetUserCount()
143![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
144
return this.m_db.Count();
145
}
146![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
147![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
148
/// 异步方式处理命令队列
149
/// </summary>
150
private void BeginProcessCmdQueue()
151![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
152
if (this.m_isBusy)
153
return;
154![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
155
// 未到可以统计的时间
156
if (DateTime.Now - this.m_lastStatisticTime < TimeSpan.FromSeconds(this.StatisticEventInterval))
157
return;
158![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
159
Thread t = null;
160![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
161
t = new Thread(new ThreadStart(this.ProcessCmdQueue));
162
t.Start();
163
}
164![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
165![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
166
/// 处理命令队列
167
/// </summary>
168
private void ProcessCmdQueue()
169![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
170
lock (this)
171![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
172
if (this.m_isBusy)
173
return;
174![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
175
// 未到可以统计的时间
176
if (DateTime.Now - this.m_lastStatisticTime < TimeSpan.FromSeconds(this.StatisticEventInterval))
177
return;
178![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
179
this.m_isBusy = true;
180![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
181
// 声明临时队列, 用于交换
182
Queue<OnlineUserCmdBase> tempQ = null;
183![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
184
// 交换两个命令队列
185
tempQ = this.m_cmdQueueA;
186
this.m_cmdQueueA = this.m_cmdQueueX;
187
this.m_cmdQueueX = tempQ;
188
tempQ = null;
189![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
190
while (this.m_cmdQueueX.Count > 0)
191![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
192
// 获取命令
193
OnlineUserCmdBase cmd = this.m_cmdQueueX.Peek();
194![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
195
if (cmd == null)
196
break;
197![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
198
// 执行命令
199
cmd.Execute();
200![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
201
// 从队列中移除命令
202
this.m_cmdQueueX.Dequeue();
203
}
204![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
205
// 清除超时用户
206
this.m_db.ClearTimeOut(this.UserTimeOutMinute);
207
// 排序
208
this.m_db.Sort();
209![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
210
this.m_lastStatisticTime = DateTime.Now;
211
this.m_isBusy = false;
212
}
213
}
214
}
215
}
命令对象,包括OnlineUserCmdBase命令基础类、OnlineUserInsertCmd插入命令、OnlineUserDeleteCmd删除命令。
1
using System;
2![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
3
namespace Net.AfritXia.Web.OnlineStat
4![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
5![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
6
/// 在线用户命令基础类
7
/// </summary>
8
internal abstract class OnlineUserCmdBase
9![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
10
// 当前用户对象
11
private OnlineUser m_currUser = null;
12
// 在线用户数据库
13
private OnlineUserDB m_db = null;
14![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
15![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
类构造器#region 类构造器
16![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
17
/// 类默认构造器
18
/// </summary>
19
public OnlineUserCmdBase()
20![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
21
}
22![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
23![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
24
/// 类参数构造器
25
/// </summary>
26
/// <param name="db">在线用户数据库</param>
27
/// <param name="currUser">当前用户</param>
28
public OnlineUserCmdBase(OnlineUserDB db, OnlineUser currUser)
29![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
30
this.OnlineUserDB = db;
31
this.CurrentUser = currUser;
32
}
33
#endregion
34![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
35![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
36
/// 设置或获取当前用户
37
/// </summary>
38
public OnlineUser CurrentUser
39![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
40
set
41![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
42
this.m_currUser = value;
43
}
44![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
45
get
46![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
47
return this.m_currUser;
48
}
49
}
50![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
51![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
52
/// 设置或获取在线用户数据库
53
/// </summary>
54
public OnlineUserDB OnlineUserDB
55![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
56
set
57![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
58
this.m_db = value;
59
}
60![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
61
get
62![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
63
return this.m_db;
64
}
65
}
66![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
67![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
68
/// 执行命令
69
/// </summary>
70
public abstract void Execute();
71
}
72
}
1
using System;
2![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
3
namespace Net.AfritXia.Web.OnlineStat
4![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
5![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
6
/// 插入命令
7
/// </summary>
8
internal class OnlineUserInsertCmd : OnlineUserCmdBase
9![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
10![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
类构造器#region 类构造器
11![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
12
/// 类默认构造器
13
/// </summary>
14
public OnlineUserInsertCmd()
15
: base()
16![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
17
}
18![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
19![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
20
/// 类参数构造器
21
/// </summary>
22
/// <param name="db">在线用户数据库</param>
23
/// <param name="currUser">当前插入的新用户</param>
24
public OnlineUserInsertCmd(OnlineUserDB db, OnlineUser currUser)
25
: base(db, currUser)
26![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
27
}
28
#endregion
29![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
30![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
31
/// 执行命令
32
/// </summary>
33
public override void Execute()
34![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
35
this.OnlineUserDB.Insert(this.CurrentUser);
36
}
37
}
38
}
1
using System;
2![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
3
namespace Net.AfritXia.Web.OnlineStat
4![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
5![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
6
/// 删除命令
7
/// </summary>
8
internal class OnlineUserDeleteCmd : OnlineUserCmdBase
9![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
10![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
类构造器#region 类构造器
11![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
12
/// 类默认构造器
13
/// </summary>
14
public OnlineUserDeleteCmd()
15
: base()
16![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
17
}
18![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
19![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
20
/// 类参数构造器
21
/// </summary>
22
/// <param name="db">在线用户数据库</param>
23
/// <param name="currUser">当前被删除的用户</param>
24
public OnlineUserDeleteCmd(OnlineUserDB db, OnlineUser currUser)
25
: base(db, currUser)
26![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
27
}
28
#endregion
29![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
30![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
31
/// 执行命令
32
/// </summary>
33
public override void Execute()
34![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
35
this.OnlineUserDB.Delete(this.CurrentUser);
36
}
37
}
38
}
最后,OnlineUserList 被封装到OnlineUserDB类中,OnlineUserDB也属于“控制器”代码如下:
1
using System;
2
using System.Collections.Generic;
3![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
4
namespace Net.AfritXia.Web.OnlineStat
5![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
6![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
7
/// 在线用户数据库
8
/// </summary>
9
internal class OnlineUserDB
10![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
11
// 在线用户集合
12
private List<OnlineUser> m_onlineUserList = null;
13![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
14![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
类构造器#region 类构造器
15![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
16
/// 类默认构造器
17
/// </summary>
18
public OnlineUserDB()
19![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
20
this.m_onlineUserList = new List<OnlineUser>();
21
}
22
#endregion
23![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
24![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
25
/// 插入新用户
26
/// </summary>
27
/// <param name="newUser"></param>
28
public void Insert(OnlineUser newUser)
29![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
30
lock (this)
31![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
32
this.m_onlineUserList.Add(newUser);
33
}
34
}
35![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
36![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
37
/// 删除用户
38
/// </summary>
39
/// <param name="delUser"></param>
40
public void Delete(OnlineUser delUser)
41![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
42
lock (this)
43![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
44
this.m_onlineUserList.RemoveAll((new PredicateDelete(delUser)).Predicate);
45
}
46
}
47![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
48![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
49
/// 清除超时用户
50
/// </summary>
51
/// <param name="timeOutMinute">超时分钟数</param>
52
public void ClearTimeOut(int timeOutMinute)
53![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
54
lock (this)
55![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
56
this.m_onlineUserList.RemoveAll((new PredicateTimeOut(timeOutMinute)).Predicate);
57
}
58
}
59![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
60![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
61
/// 排序在线用户列表
62
/// </summary>
63
public void Sort()
64![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
65
// 按活动时间进行排序
66
this.m_onlineUserList.Sort(CompareByActiveTime);
67
}
68![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
69![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
70
/// 获取所有用户
71
/// </summary>
72
/// <returns></returns>
73
public IList<OnlineUser> Select()
74![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
75
return this.m_onlineUserList.ToArray();
76
}
77![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
78![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
79
/// 获取在线用户数量
80
/// </summary>
81
/// <returns></returns>
82
public int Count()
83![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
84
return this.m_onlineUserList.Count;
85
}
86![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
87![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
用户删除条件断言#region 用户删除条件断言
88
private class PredicateDelete
89![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
90
// 被删除的用户
91
private OnlineUser m_delUser = null;
92
// 是否为空条件
93
private bool m_isNullCondation = true;
94![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
95![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
类构造器#region 类构造器
96![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
97
/// 类参数构造器
98
/// </summary>
99
/// <param name="delUser"></param>
100
public PredicateDelete(OnlineUser delUser)
101![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
102
this.m_delUser = delUser;
103![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
104
if (this.m_delUser == null)
105
return;
106![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
107
// 用户 ID
108
this.m_isNullCondation &= this.m_delUser.UniqueID <= 0;
109
// 名称
110
this.m_isNullCondation &= String.IsNullOrEmpty(this.m_delUser.UserName);
111
// SessionID
112
this.m_isNullCondation &= String.IsNullOrEmpty(this.m_delUser.SessionID);
113
}
114
#endregion
115![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
116![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
117
/// 判断用户 ID 是否等于指定值
118
/// </summary>
119
/// <param name="user"></param>
120
/// <returns></returns>
121
public bool Predicate(OnlineUser user)
122![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
123
if (this.m_isNullCondation)
124
return false;
125![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
126
if (user == null)
127
return false;
128![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
129
// 用户 ID 相同, ID > 0
130
if (user.UniqueID > 0 && user.UniqueID == this.m_delUser.UniqueID)
131
return true;
132![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
133
// 用户名称相同, 并且不是空字符串
134
if (!String.IsNullOrEmpty(user.UserName) && user.UserName == this.m_delUser.UserName)
135
return true;
136![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
137
// SessionID 相同, 并且不是空字符串
138
if (user.SessionID == this.m_delUser.SessionID)
139
return true;
140![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
141
return false;
142
}
143
}
144
#endregion
145![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
146![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
用户超时条件断言#region 用户超时条件断言
147
private class PredicateTimeOut
148![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
149
// 超时分钟数
150
private int m_timeOutMinute;
151![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
152![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
类构造器#region 类构造器
153![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
154
/// 类参数构造器
155
/// </summary>
156
/// <param name="minute">超时分钟数</param>
157
public PredicateTimeOut(int minute)
158![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
159
this.m_timeOutMinute = minute;
160
}
161
#endregion
162![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
163![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
164
/// 判断用户活动时间是否小于指定值
165
/// </summary>
166
/// <param name="user"></param>
167
/// <returns></returns>
168
public bool Predicate(OnlineUser user)
169![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
170
if (user == null)
171
return false;
172![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
173
return user.LastActiveTime < DateTime.Now.AddMinutes(-this.m_timeOutMinute);
174
}
175
}
176
#endregion
177![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
178![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
179
/// 比较两个用户的活动时间
180
/// </summary>
181
/// <param name="x"></param>
182
/// <param name="y"></param>
183
/// <returns></returns>
184
private static int CompareByActiveTime(OnlineUser x, OnlineUser y)
185![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
186
if (x == null)
187
throw new NullReferenceException("X 值为空 ( X Is Null )");
188![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
189
if (y == null)
190
throw new NullReferenceException("Y 值为空 ( Y Is Null )");
191![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
192
if (x.LastActiveTime > y.LastActiveTime)
193
return -1;
194![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
195
if (x.LastActiveTime < y.LastActiveTime)
196
return +1;
197![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
198
return 0;
199
}
200
}
201
}
关于本文更详细的代码,请参见代码包WebUI项目中的DefaultLayout.master.cs文件。