[分享]一个在线用户列表的解决方案(解决用户意外退出在线列表无法及时更新问题) 顺便散分 ~~

cncxz 2005-07-18 11:14:10
最近所做的一个项目需要用到的在线用户列表,上网搜索了一下发现现有的解决方案对用户意外退出的处理均不是太理想。一般来说,用户离开系统的方式有三种:主动注销、会话超时、直接关闭浏览器,对于前两种,我们很容易便可将该用户从在线列表中清除,关键是第三种(很多用户都是直接关闭窗口的~~郁闷ing),程序无法捕获窗口关闭的精确时间,只能等到会话超时后在能将该用户清除出在线列表,假设我们设置会话超时时间为60分钟,而用户登陆系统随便浏览一个页面就以关闭浏览器的方式退出的话,我们要在将近1小时后才能从在线列表中将该用户清除出去(想象一下,系统显示n多人在线,可能除了你之外其他的n-1人都关机走人了,汗一个先```),而本文将尝试寻找一个解决方案把这种尴尬降至最低。
我的大概思路是,给每在线用户增加一个RefreshTime属性,建立一个负责将当前用户的RefreshTime属性设置为当前时间的单独页面(Refresh.aspx),然后在系统的主要页面(也可以是所有页面)中通过xmlhttp不断地请求Refresh.aspx页面,一旦用户关闭了与本系统相关的所有窗口,即以直接关闭浏览器的方式退出系统,那么该用户的RefreshTime属性便不会自动更新了,我们再设置一个自动刷新的超时时间(这个要比会话超时短很多_refreshTimeout),当发现某用户超过_refreshTimeout的时间没有自动刷新,就能判定该用户已经以直接关闭浏览器的方式退出了。
假设我们设置会话超时时间为60分钟,自动刷新超时时间为1分钟,在客户端通过xmlhttp每隔25秒(之所以不设1分钟,是防止网速慢的时候访问Refresh.aspx超时,个人感觉,不一定正确)访问一次Refresh.aspx页面,在用户登陆、用户注销、检测用户是否在线的时候都执行清理超时用户(包括会话超时和自动刷新超时)操作,这样一来,在线用户列表的统计误差就由60分钟降至1分钟了。

全文地址:http://blog.csdn.net/cncxz/archive/2005/07/18/427778.aspx
...全文
1054 30 打赏 收藏 转发到动态 举报
写回复
用AI写文章
30 条回复
切换为时间正序
请发表友善的回复…
发表回复
ll_e_mail 2005-09-22
  • 打赏
  • 举报
回复
很好
peterHunter 2005-07-21
  • 打赏
  • 举报
回复
学习,接分
huangkc 2005-07-21
  • 打赏
  • 举报
回复

我觉得利用XML
采用定时刷新等方法也可以
不管哪种方法,最重要的还是要注意效率
尤其用户多时
rijcm 2005-07-21
  • 打赏
  • 举报
回复
正好需要这玩意~谢谢楼主
cncxz 2005-07-21
  • 打赏
  • 举报
回复
这个项目还需要弄个在线计算器,搜索了半天也就126信箱里提供那个还不错,只是对form的依赖性太强(form的name必须为calc),感觉及其不爽,于是调整了一下他的js代码,简单封装成了一个dll,需要web计算器功能又懒得调整js代码的兄弟可以直接拿去调用。

http://blog.csdn.net/cncxz/archive/2005/07/20/429952.aspx
layueer 2005-07-18
  • 打赏
  • 举报
回复
学习……
BossFriday 2005-07-18
  • 打赏
  • 举报
回复
这个方法看过相关介绍的文章,没有在实际项目大数据量下应用过.
帮顶,讨论.
gyf19 2005-07-18
  • 打赏
  • 举报
回复
学习!!
cncxz 2005-07-18
  • 打赏
  • 举报
回复
感谢 hackate(兰花开香入梦境,独思佳人亦飘然!!)

学习ing
jimu8130 2005-07-18
  • 打赏
  • 举报
回复
并发性是个问题
hackate 2005-07-18
  • 打赏
  • 举报
回复
贴出你看看吧
在线用户列表的实现
在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的做法实现的。我的算法是另一种思想的算法!
jimu8130 2005-07-18
  • 打赏
  • 举报
回复
学习
hackate 2005-07-18
  • 打赏
  • 举报
回复
有个朋友有做过这个,好像挺不错的。用HashTable的方法。回头找一下文章。你这个的话,如果说用户多的话,不是会有很大的影响了?
codeangel 2005-07-18
  • 打赏
  • 举报
回复
大至思路也就是这样,学习+接分
cncxz 2005-07-18
  • 打赏
  • 举报
回复
使用一个静态的datatable存储在线用户列表,所以会占用一部分服务器内存

用xmlhttp定时循环刷新会占用一部分客户端资源,

对效率应该会有些影响,但感觉影响不是太大,尚可忍受

目前我只能想到这种方法实现,大家有好的方案可以一起研究

共同学习~~
wanghaiwei 2005-07-18
  • 打赏
  • 举报
回复
先看看。学习中。
jsyhello 2005-07-18
  • 打赏
  • 举报
回复
会不会影响效率?
cncxz 2005-07-18
  • 打赏
  • 举报
回复
To:liuqinglq(白菜) :

虽然window.onbeforeunload 不是只有关闭IE才会触发

但是他和window.onload 应该是成对出现的(我没想到特殊情况)

所以应该在window.onload 和window.onbeforeunload操作当前用户打开窗口数目属性,

要是在后台代码的Page_Load中执行当前用户打开窗口数目属性加1的话就会发生紊乱

个人观点~~ 继续讨论啊
shilei831115 2005-07-18
  • 打赏
  • 举报
回复
学习
liuqinglq 2005-07-18
  • 打赏
  • 举报
回复
仔细看了一下

关于 window.onbeforeunload 这个事件,大家可以去查查MSDN

这个事件可不是只有关闭IE才会触发的...还有很多方式可以触发,用这个事件来判断IE关闭是不正确的

就算加上鼠标坐标的判断也是不行的...关闭按钮可不会固定在一个位置

鼠标在关闭按钮上也不一定就是在点击关闭按钮

呵呵
加载更多回复(10)

62,074

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术交流专区
javascript云原生 企业社区
社区管理员
  • ASP.NET
  • .Net开发者社区
  • R小R
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

.NET 社区是一个围绕开源 .NET 的开放、热情、创新、包容的技术社区。社区致力于为广大 .NET 爱好者提供一个良好的知识共享、协同互助的 .NET 技术交流环境。我们尊重不同意见,支持健康理性的辩论和互动,反对歧视和攻击。

希望和大家一起共同营造一个活跃、友好的社区氛围。

试试用AI创作助手写篇文章吧