zoukankan
html css js c++ java
[总结]关于在线用户列表的统计!
1
。在线用户列表的实现
在ASP时代,要实现一个网站的在线用户列表显示功能的惯用做法是修改global.asa文件中的:Application_Start、Session_Start和Session_End这三个函数。在ASP.NET时代,我依然这样做。但是必须注意很多问题。首先来看看最简单的代码实现:
protected
void
Application_Start(Object sender, EventArgs e)
{
Application.Lock();
Application[
"
OnlineUsers
"
]
=
null
;
Application.UnLock();
}
protected
void
Session_Start(Object sender, EventArgs e)
{
Application.Lock();
if
(Application[
"
OnlineUsers
"
]
==
null
)
Application[
"
OnlineUsers
"
]
=
new
Hashtable();
Hashtable onlineUsersHash
=
(Hashtable)Application[
"
OnlineUsers
"
];
onlineUsersHash.Add(Request.UserHostAddress, Request.Cookies[
"
UserName
"
].Value);
Application.UnLock();
}
protected
void
Session_End(Object sender, EventArgs e)
{
Hashtable onlineUsersHash
=
(Hashtable)Application[
"
OnlineUsers
"
];
onlineUsersHash.Remove(Request.UserHostAddress);
}
这就是一个简单的能实现记录在线用户列表的代码。呵呵,简单吧?你可以传到服务器上去试试!如果你和我一样,看到自己的用户名已经出现在列表中,就欢呼雀跃地告诉许多网友很简单就实现了一个在线用户列表显示功能,然后就关了机器去睡觉了的话,那么第二天清晨你会大吃一惊!你的网站上的在线用户列表中的人名会多的数不清,而且你会知道其实那些人根本就不在线上… 哦,真是个灾难!算法思想没有任何错误,但是却得出错误的结果,为什么呢?虽然是一个小小的功能,但是却隐藏了许多玄机,这个就要看你是否能解开了…
首先我要肯定一点,用Hashtable这样的数据结构来存储在线用户的名称的确是个不错的选择。主键使用用户的IP地址,主键值存放用户名称。因为网络中的IP地址是唯一的,所以用它来充当主键时对的。出现上述错误的原因是在Session_End函数中,Hashtable没有将主键删除掉?!
我想所有初学者都会和我一样,会问:既然IP地址在整个网络中是唯一的,那为什么还无法删除Hashtable中的键呢?答案是:Hashtable没有找到主键名,即用户的IP地址:Request.UserHostAddress!这听起来真是笑话,用户IP地址怎么会找不到呢?只要用户登陆Internet,就必有IP地址!它怎么会找不到呢?
我告诉你,原因是:用户根本就没有登陆Internet!
什么在线,又不在线的?我想你现在应该已经晕了… 不过,看了下面的图例,我想你就明白了…
如果觉得示意图有些小,可以调整显示比例(Word菜单 à 视图 à 显示比例)。
左图是假设一个用户先登录Bincess论坛,而后就去了WadeLau.org(WadeLau.net)这个网站。但是他一直没有断开连接,一直都在Internet上。而当AfritXia.net的服务器结束了用户的Session时,就会调用:
protected
void
Session_End(Object sender, EventArgs e)
{
Hashtable onlineUsersHash
=
(Hashtable)Application[
"
OnlineUsers
"
];
onlineUsersHash.Remove(Request.UserHostAddress);
}
来清除在线用户列表中的用户名称,这样做是对的!
而右图,则是用户在AfritXia.net服务器结束用户的Session之前就已经断开连接离开Internet了。那么服务器端在获取用户的IP地址时,会是什么结果呢?我也不知道会是什么结果,但总之,肯定不是我们想要的结果,也不会出现在onlineUsersHash数据结构中主键名称中。
就是这样,由于找不到主键名称,所以onlineUsersHash就无法移除对应的值,所以就出现了已经离线的人的名字还出现在在线用户列表中(这样的情况被我称之为:僵尸)。
只要知道问题所在,那么就能想出解决办法。对于这个问题来说,不幸的是它已经被发现了。那么解决它的对策也就很快地被制定出来了。在新的算法中采用SessionID来作为主键来记录用户登录信息。但是还有很多问题需要注意!例如:一个打开的IE浏览器,服务器会给它分配一个SessionID,但是再次打开一个新的IE浏览器,服务器照旧还是要给它分配一个SessionID。这就是说同一个用户、同一个PC机、同一个IP地址,服务器却给它分配了多个Session。IE对服务器的请求能力也太强了?!因为一个开启的IE浏览器在计算机里就是一个进程,服务器给客户端的一个进程分配Session乃是天经地义。而如果是使用MyIE,它是MDI程序,不管开多少个子窗口,都只是属于一个进程。所以对于MyIE,ASP.NET只给了它一个Session。注意!这也是为什么在MyIE中时而会出现在线用户0人的一个原因,虽然你还在线上。还有个问题,例如:一个用户刚登陆Bincess不久,就因为线路故障掉下线去了。可是没过多久他就回来了,而此时他的Cookie还没过期,但是IP地址和SessionID全变了。而如果只考虑用SessionID来记录在线用户列表的话,对于这种情况就会出现一个用户名称出现两次的尴尬。还是以一个示意图来说明新算法的情况:
示意图中的SessionID_1和SessionID_2说明UserName_1开启了两个IE窗体。
建立两个个哈希表结构OnlineUsersHash和OnlineUsers_SessionIPHash,当用户访问Bincess时,会为他分配一个SessionID。令用户的IP地址和用户名称建立一个一一对应的关系。如果用户开启了新的窗口,则检查用户的IP地址或用户名是否已经在OnlineUsersHash中出现过?如果出现过,就让新的SessionID指向现有的IP地址。而当一个Session结束时,则将该SessionID从OnlineUsers_SessionIPHash中移除。判断是否还有其他SessionID指向这个IP地址,如果没有,那么从在线用户列表中移除用户名称。客户端的情况相当复杂,必须要考虑周全。下面则是新的算法的代码:
//
在Global.asax.cs 文件中
//
//
在线用户列表主键名
public
const
string
KEY_ONLINEUSERS
=
"
OnlineUsers
"
;
//
在线用户列表 Session 表主键名
public
const
string
KEY_ONLINEUSERS_SESSIONIP
=
"
OnlineUsers_SessionIP
"
;
protected
void
Application_Start(Object sender, EventArgs e)
{
Application.Lock();
Application[KEY_ONLINEUSERS]
=
null
;
Application[KEY_ONLINEUSERS_SESSIONIP]
=
null
;
//
目的是将用户的SessionID和IP对应起来
Application.UnLock();
}
protected
void
Session_Start(Object sender, EventArgs e)
{
Application.Lock();
/**/
/*
*/
Hashtable onlineUsersHash
=
(Hashtable)Application[KEY_ONLINEUSERS];
Hashtable onlineUsersSessionIPHash
=
(Hashtable)Application[KEY_ONLINEUSERS_SESSIONIP];
if
(Visitor.Current.IsGuest)
//
如果用户是来宾
{
if
(onlineUsersHash.ContainsKey(Request.UserHostAddress))
{
onlineUsersHash[Request.UserHostAddress]
=
""
;
}
else
{
onlineUsersHash.Add(Request.UserHostAddress,
""
);
}
}
else
{
if
(
!
onlineUsersHash.ContainsKey(Request.UserHostAddress)
&&
!
onlineUsersHash.ContainsValue(Visitor.Current.UserName))
{
//
如果用户的 IP 地址和用户名称在列表中找不到,则将添加在线用户列表中
onlineUsersHash.Add(Request.UserHostAddress, Request.Cookies[″UserName″].Value);
}
else
if
(onlineUsersHash.ContainsValue(Request.Cookies[“UserName”].Value))
{
//
如果用户的 Cookie 信息能够找到,则更新(先删除再添加)在线用户的 IP 地址
//
//
说明:用户可能刚登陆不久,便因为线路故障,断线并重新拨号
//
而当用户回到网站时,用户的 Cookie 还未过期,但是 IP 地址却发生了改变
string
userName
=
Request.Cookies[″UserName″].Value;
foreach
(
object
key
in
onlineUsersHash.Keys)
{
if
(((
string
)onlineUsersHash[key]).Equals(userName))
{
//
删除用户刚才使用过的 IP 地址
onlineUsersHash.Remove(key);
break
;
}
}
//
添加在线用户
onlineUsersHash.Add(Request.UserHostAddress, Request.Cookies[″UserName″].Value);
}
else
if
(onlineUsersHash.ContainsKey(Request.UserHostAddress))
{
//
如果用户的 IP 地址能找到,则更新在线用户的名称
//
//
说明:用户登录后,注销并重新登陆。可能是去换个用户名
onlineUsersHash[Request.UserHostAddress]
=
Request.Cookies[″UserName″].Value;
}
}
//
将用户的 IP 地址和 SessionID 对应起来
if
(
!
onlineUsersSessionIPHash.ContainsKey(Session.SessionID))
onlineUsersSessionIPHash.Add(Session.SessionID, Request.UserHostAddress);
Application.UnLock();
}
protected
void
Session_End(Object sender, EventArgs e)
{
Application.Lock();
if
(Application[KEY_ONLINEUSERS]
!=
null
)
{
Hashtable onlineUsersHash
=
(Hashtable)Application[KEY_ONLINEUSERS];
Hashtable onlineUsersSessionIPHash
=
(Hashtable)Application[KEY_ONLINEUSERS_SESSIONIP];
//
获取用户的IP地址
string
IP
=
(
string
)onlineUsersSessionIPHash[Session.SessionID];
//
移除用户的IP地址
onlineUsersSessionIPHash.Remove(Session.SessionID);
//
如果没有一个Session指向这个IP了,则说明这个用户确实已经离开了网站
//
可以删除该用户的用户名称了
if
(
!
onlineUsersSessionIPHash.ContainsValue(IP))
onlineUsersHash.Remove(IP);
}
Application.UnLock();
}
有很多其它的在线用户列表的算法,但多半都是要借助数据库才可以。CSDN上的一个网友写了一个比较精确的算法,是通过记录用户每次最后活动的时间来定时地、不断刷地新DataSet的做法实现的。我的算法是另一种思想的算法!
看了一下yangzixp(扬子(四川·巴中),原理基本一致,不同的是你使用的FORMS身份验证,而且可以改进
--
每次Application_AuthenticateRequest就检查并删除超时用户,肯定是太频繁了,改用Timer吧
总的来说,要做个在线人数统计简单,但是要做在线名单并且保存用户的访问日志,就需要耗费比较多的系统资源,是否划算就难说了(我只看需求文档,其他不管
);
前面用过的IHttpModule方法也不错,原先每用过,也学了一招
感谢思归老大的帮忙,分就散了吧
~
using
System;
using
System.ComponentModel;
using
System.Web;
using
System.Web.SessionState;
using
System.Data;
using
System.Data.OleDb;
namespace
XsExam
{
/**/
///
<summary>
///
Global 的摘要说明。
///
</summary>
public
class
Global : System.Web.HttpApplication
{
private
static
System.Threading.Timer timer;
private
const
int
interval
=
1000
*
60
*
10
;
//
检查在线用户的间隔时间
/**/
///
<summary>
///
必需的设计器变量。
///
</summary>
private
System.ComponentModel.IContainer components
=
null
;
public
Global()
{
InitializeComponent();
}
protected
void
Application_Start(Object sender, EventArgs e)
{
if
(timer
==
null
)
timer
=
new
System.Threading.Timer(
new
System.Threading.TimerCallback(ScheduledWorkCallback),
sender,
0
, interval);
DataTable userTable
=
new
DataTable();
userTable.Columns.Add(
"
UserID
"
);
//
用户ID
userTable.Columns.Add(
"
UserName
"
);
//
用户姓名
userTable.Columns.Add(
"
FirstRequestTime
"
);
//
第一次请求的时间
userTable.Columns.Add(
"
LastRequestTime
"
);
//
最后一次请求的时间
userTable.Columns.Add(
"
ClientIP
"
);
//
userTable.Columns.Add(
"
ClientName
"
);
//
userTable.Columns.Add(
"
ClientAgent
"
);
//
//
userTable.Columns.Add("LastRequestPath");
//
最后访问的页面
userTable.PrimaryKey
=
new
DataColumn[]
{userTable.Columns[
0
]}
;
userTable.AcceptChanges();
Application.Lock();
Application[
"
UserOnLine
"
]
=
userTable;
Application.UnLock();
}
protected
void
Session_Start(Object sender, EventArgs e)
{
}
protected
void
Application_BeginRequest(Object sender, EventArgs e)
{
}
protected
void
Application_EndRequest(Object sender, EventArgs e)
{
}
protected
void
Application_AcquireRequestState(Object sender, EventArgs e)
{
HttpApplication mApp
=
(HttpApplication)sender;
if
(mApp.Context.Session
==
null
)
return
;
if
(mApp.Context.Session[
"
UserID
"
]
==
null
)
return
;
string
userID
=
mApp.Context.Session[
"
UserID
"
].ToString();
DataTable userTable
=
(DataTable)Application[
"
UserOnLine
"
];
DataRow curRow
=
userTable.Rows.Find(
new
object
[]
{userID}
);
if
(curRow
!=
null
)
{
this
.GetDataRowFromHttpApp(mApp,
ref
curRow);
}
else
{
DataRow newRow
=
userTable.NewRow();
this
.GetDataRowFromHttpApp(mApp,
ref
newRow);
userTable.Rows.Add(newRow);
}
userTable.AcceptChanges();
Application.Lock();
Application[
"
UserOnLine
"
]
=
userTable;
Application.UnLock();
}
protected
void
Application_AuthenticateRequest(Object sender, EventArgs e)
{
}
protected
void
Application_Error(Object sender, EventArgs e)
{
}
protected
void
Session_End(Object sender, EventArgs e)
{
}
protected
void
Application_End(Object sender, EventArgs e)
{
}
Web 窗体设计器生成的代码
#region
Web 窗体设计器生成的代码
/**/
///
<summary>
///
设计器支持所需的方法 - 不要使用代码编辑器修改
///
此方法的内容。
///
</summary>
private
void
InitializeComponent()
{
this
.components
=
new
System.ComponentModel.Container();
}
#endregion
private
void
GetDataRowFromHttpApp(HttpApplication mApp,
ref
DataRow mRow)
{
if
(mApp.Context.Session
==
null
)
return
;
if
(mApp.Context.Session[
"
UserID
"
]
==
null
||
mApp.Context.Session[
"
UserName
"
]
==
null
)
return
;
string
userID
=
mApp.Context.Session[
"
UserID
"
].ToString();
string
userName
=
mApp.Context.Session[
"
UserName
"
].ToString();
//
string requestPath = mApp.Request.Path;
if
(mRow[
"
UserID
"
].ToString().Length
<
1
)
{
mRow[
"
UserID
"
]
=
userID;
mRow[
"
UserName
"
]
=
userName;
mRow[
"
FirstRequestTime
"
]
=
System.DateTime.Now;
mRow[
"
ClientIP
"
]
=
mApp.Context.Request.UserHostAddress;
mRow[
"
ClientName
"
]
=
mApp.Context.Request.UserHostName;
mRow[
"
ClientAgent
"
]
=
mApp.Context.Request.UserAgent;
}
mRow[
"
LastRequestTime
"
]
=
System.DateTime.Now;
//
mRow["LastRequestPath"] = requestPath;
}
private
void
ScheduledWorkCallback (
object
sender)
{
string
filter
=
"
Convert(LastRequestTime,'System.DateTime') < Convert('
"
+
System.DateTime.Now.AddSeconds(
-
interval
/
1000
).ToString()
+
"
','System.DateTime')
"
;
DataTable userTable
=
(DataTable)Application[
"
UserOnLine
"
];
DataRow[] lineOutUsers
=
userTable.Select(filter);
for
(
int
i
=
0
;i
<
lineOutUsers.Length;i
++
)
{
DataRow curRow
=
lineOutUsers[i];
//
保存到数据库
XsStudio.Database db
=
new
XsStudio.Database();
curRow.Delete();
}
userTable.AcceptChanges();
Application.Lock();
Application[
"
UserOnLine
"
]
=
userTable;
Application.UnLock();
}
}
}
按照思归老大的提点,修改方案如下:
使用IHttpModule,加分讨论
首先创建实现IHttpModule接口的类MyModule:
using
System;
using
System.Web;
using
System.Data;
namespace
Test2004_5_13
{
public
class
MyModule : IHttpModule
{
public
void
Init(HttpApplication application)
{
application. AcquireRequestState
+=
(
new
EventHandler(
this
.Application_AcquireRequestState));
}
private
void
Application_AcquireRequestState (Object source,
EventArgs e)
{
HttpApplication mApplication
=
(HttpApplication)source;
HttpResponse Response
=
mApplication.Context.Response;
DataTable dt
=
null
;
if
(mApplication.Context.Application[
"
UserOnLine
"
]
!=
null
)
{
dt
=
(DataTable)mApplication.Context.Application[
"
UserOnLine
"
];
}
else
{
dt
=
new
DataTable();
dt.Columns.Add(
"
UserName
"
);
dt.Columns.Add(
"
FirstLoadTime
"
);
dt.Columns.Add(
"
LastLoadTime
"
);
}
//
将当前用户添加到在线用户列表
if
(mApplication.Context.Session
!=
null
)
{
if
(mApplication.Context.Session[
"
UserID
"
]
!=
null
)
{
string
userName
=
mApplication.Context.Session[
"
UserID
"
].ToString();
DataRow[] rows
=
dt.Select(
"
UserName='
"
+
userName
+
"
'
"
);
if
(rows.Length
>
0
)
rows[
0
][
2
]
=
System.DateTime.Now.ToString();
else
dt.Rows.Add(
new
object
[]
{userName,System.DateTime.Now.ToString(),System.DateTime.Now.ToString()}
);
}
}
dt.AcceptChanges();
mApplication.Context.Application[
"
UserOnLine
"
]
=
dt;
Response.Write(
"
Beginning of Request
"
+
dt.Rows.Count.ToString());
}
public
void
Dispose()
{
}
}
}
2
)在web.config中注册
<
httpModules
>
<
add type
=
"
Test2004_5_13.MyModule,Test2004_5_13
"
name
=
"
MyModule
"
/>
</
httpModules
>
查看全文
相关阅读:
Java中@Override的作用
微软面试题: LeetCode 152. 乘积最大子数组 出现次数:2
微软面试题: LeetCode 300. 最长递增子序列 出现次数:2
微软面试题: LeetCode 76. 最小覆盖子串 出现次数:2
微软面试题:剑指 Offer 52. 两个链表的第一个公共节点 出现次数:2
微软面试题: LeetCode 79. 单词搜索 出现次数:2
微软面试题: LeetCode 39. 组合总和 出现次数:2
微软面试题: LeetCode 151. 翻转字符串里的单词 出现次数:2
微软面试题: LeetCode 415. 字符串相加 出现次数:2
微软面试题: LeetCode 110. 平衡二叉树 出现次数:2
原文地址:https://www.cnblogs.com/kokoliu/p/618140.html
最新文章
509 Bandwidth Limit Exceeded 服务器达到带宽限制。这不是一个官方的状态码,但是仍被广泛使用。
磁盘IO测试工具 fio
内存泄漏检测工具valgrind 热点函数以及CPU使用率 性能评估
分布式锁的技术选型及思考
Select for update使用详解 InnoDB Locking
Go中内存分配源码实现
货 | Trip.com APP 启动优化实践 原创 Shanks 携程技术 2021-06-03
context deadline exceeded
/ Canceled if the context was canceled // or DeadlineExceeded if the context's deadline passed. // After Err
mysql锁(何登成)
热门文章
《面试篇》Http协议
通俗大白话来理解TCP协议的三次握手和四次分手
key.hashCode() & 0x7fffffff
# Explain 详解(下)
SpringBoot操作ES进行各种高级查询
面试博客
netty博客
java判断数组是否相同
java二分法查询
java通过java.util.Arrays.sort()排序
Copyright © 2011-2022 走看看