最近所做的一个项目需要用到的在线用户列表,上网搜索了一下发现现有的解决方案对用户意外退出的处理均不是太理想。一般来说,用户离开系统的方式有三种:主动注销、会话超时、直接关闭浏览器,对于前两种,我们很容易便可将该用户从在线列表中清除,关键是第三种(很多用户都是直接关闭窗口的~~郁闷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分钟了。

==========================================

具体实现如下:

1、 新建一个名为ActiveUser的类,存储单个活动用户数据。

/// <summary>
 /// 单个在线用户数据,无法继承此类。
 /// </summary>
 public sealed class ActiveUser
 { 
  private readonly string _ticket;    //票据名称
  private readonly string _username;   //登陆用户名
  private readonly string _truename;   //登陆用户名
  private readonly string _roleid;    //角色
  private readonly DateTime _refreshtime;  //最新刷新时间
  private readonly DateTime _activetime;  //最新活动时间
  private readonly string _clientip;   //登陆IP
 
  public ActiveUser(string Ticket,string UserName,string TrueName,string RoleID,string ClientIP) {
   this._ticket=Ticket;
   this._username=UserName;
   this._truename=TrueName;
   this._roleid=RoleID;
   this._refreshtime=DateTime.Now;
   this._activetime=DateTime.Now;
   this._clientip=ClientIP;
  }

public ActiveUser(string Ticket,string UserName,string TrueName,string RoleID,DateTime RefreshTime,DateTime ActiveTime,string ClientIP)  {
   this._ticket=Ticket;
   this._username=UserName;
   this._truename=TrueName;
   this._roleid=RoleID;
   this._refreshtime=RefreshTime;
   this._activetime=ActiveTime;
   this._clientip=ClientIP;
  }
 
  public string Ticket  { get{return _ticket;}  }
  public string UserName  { get{return _username;}  }
  public string TrueName  { get{return _truename;}  }
  public string RoleID  { get{return _roleid;}  }
  public DateTime RefreshTime { get{return _refreshtime;} }
  public DateTime ActiveTime { get{return _activetime;} }
  public string ClientIP  { get{return _clientip;}  }

}

2、 新建一个名为PassPort的类,存储在线用户列表。

/// <summary>
 /// PassPort 存储在线用户列表。
 /// </summary>
 public class PassPort
 {
  private  static  DataTable  _activeusers;
  private  int  _activeTimeout;
  private  int  _refreshTimeout;

/// <summary>
  /// 初始化在线用户表。
  /// </summary>
  private void userstableFormat()
  {
   if(_activeusers==null) {
    _activeusers  =  new  DataTable("ActiveUsers");
    DataColumn  myDataColumn;
    System.Type mystringtype;
    mystringtype = System.Type.GetType("System.String");
    System.Type mytimetype;
    mytimetype = System.Type.GetType("System.DateTime");
    myDataColumn  =  new  DataColumn("Ticket",mystringtype);
    _activeusers.Columns.Add(myDataColumn);
    myDataColumn  =  new  DataColumn("UserName",mystringtype);
    _activeusers.Columns.Add(myDataColumn);
    myDataColumn  =  new  DataColumn("TrueName",mystringtype);
    _activeusers.Columns.Add(myDataColumn);
    myDataColumn  =  new  DataColumn("RoleID",mystringtype);
    _activeusers.Columns.Add(myDataColumn);   
    myDataColumn  =  new  DataColumn("RefreshTime",mytimetype);
    _activeusers.Columns.Add(myDataColumn);
    myDataColumn  =  new  DataColumn("ActiveTime",mytimetype);
    _activeusers.Columns.Add(myDataColumn);
    myDataColumn  =  new  DataColumn("ClientIP",mystringtype);
    _activeusers.Columns.Add(myDataColumn);  
   }
  }

public PassPort()
  {
   userstableFormat(); //初始化在线用户表
   //活动超时时间初始化 单位:分钟
   try { _activeTimeout=int.Parse(ConfigurationSettings.AppSettings["ActiveTimeout"]); }
   catch{ _activeTimeout=60; }  
   //自动刷新超时时间初始化 单位:分钟
   try { _refreshTimeout=int.Parse(ConfigurationSettings.AppSettings["RefreshTimeout"]); }
   catch{ _refreshTimeout=1; }  
  }

//全部用户列表
  public  DataTable  ActiveUsers
  {
   get{return  _activeusers.Copy();}
  }
 
  /// <summary>
  /// 新用户登陆。
  /// </summary>
  public void Login(ActiveUser user,bool SingleLogin)
  {
   DelTimeOut();  //清除超时用户
   if(SingleLogin){
    //若是单人登陆则注销原来登陆的用户
    this.Logout(user.UserName,false);
   }
   DataRow myRow;
   try
   {
    myRow  =  _activeusers.NewRow();   
    myRow["Ticket"]  =  user.Ticket.Trim();
    myRow["UserName"]  =  user.UserName.Trim();
    myRow["TrueName"]  =  ""+user.TrueName.Trim();
    myRow["RoleID"]  =  ""+user.RoleID.Trim();
    myRow["ActiveTime"]  =  DateTime.Now;
    myRow["RefreshTime"]  =  DateTime.Now;
    myRow["ClientIP"]  =  user.ClientIP.Trim();
    _activeusers.Rows.Add(myRow);
   }
   catch(Exception  e)
   {
    throw(new  Exception(e.Message));
   } 
   _activeusers.AcceptChanges();
  
  }

/// <summary>
  ///用户注销,根据Ticket或UserName。
  /// </summary>
  private void Logout(string strUserKey,bool byTicket)
  {
   DelTimeOut();  //清除超时用户
   strUserKey=strUserKey.Trim();
   string  strExpr;  
   strExpr =byTicket ? "Ticket=" + strUserKey +"" : "UserName=" + strUserKey + "";
   DataRow[]  curUser;
   curUser  =  _activeusers.Select(strExpr);
   if  (curUser.Length  >0  )
   {
    for(int  i  =  0;  i  <  curUser.Length;  i  ++)
    {
     curUser[i].Delete();
    }
   }
   _activeusers.AcceptChanges();  
  }

/// <summary>
  ///用户注销,根据Ticket。
  /// </summary>
  /// <param name="strTicket">要注销的用户Ticket</param>
  public void Logout(string strTicket){
   this.Logout(strTicket,true);
  }

/// <summary>
  ///清除超时用户。
  /// </summary>
  private  bool DelTimeOut()
  {  
   string  strExpr;  
   strExpr = "ActiveTime < " + DateTime.Now.AddMinutes( 0 - _activeTimeout) + "or RefreshTime < "+DateTime.Now.AddMinutes( 0 - _refreshTimeout)+"";  
   DataRow[]  curUser;
   curUser  =  _activeusers.Select(strExpr);
   if  (curUser.Length  >0  )
   {
    for(int  i  =  0;  i  <  curUser.Length;  i  ++)
    {
     curUser[i].Delete();    
    }
   }
   _activeusers.AcceptChanges();
   return  true;
  }

/// <summary>
  ///更新用户活动时间。
  /// </summary>
  public  void  ActiveTime(string  strTicket)
  {
   DelTimeOut();
   string  strExpr;
   strExpr  =  "Ticket="  +  strTicket  +  ""; 
   DataRow[]  curUser;
   curUser  =  _activeusers.Select(strExpr);
   if  (curUser.Length  >0  )
   {
    for(int  i  =  0;  i  <  curUser.Length;  i  ++)
    {
     curUser[i]["ActiveTime"]=DateTime.Now;
     curUser[i]["RefreshTime"]=DateTime.Now;
    }
   }
   _activeusers.AcceptChanges();
  }

/// <summary>
  ///更新系统自动刷新时间。
  /// </summary>
  public  void  RefreshTime(string  strTicket)
  {
   DelTimeOut();
   string  strExpr;
   strExpr  =  "Ticket="  +  strTicket  +  ""; 
   DataRow[]  curUser;
   curUser  =  _activeusers.Select(strExpr);
   if  (curUser.Length  >0  )
   {
    for(int  i  =  0;  i  <  curUser.Length;  i  ++)
    {
     curUser[i]["RefreshTime"]=DateTime.Now;
    }
   }
   _activeusers.AcceptChanges();
  }

private ActiveUser SingleUser(string strUserKey,bool byTicket)
  {
   strUserKey=strUserKey.Trim();
   string  strExpr;
   ActiveUser myuser;
   strExpr =byTicket ? "Ticket=" + strUserKey +"" : "UserName=" + strUserKey + "";
   DataRow[]  curUser;
   curUser  =  _activeusers.Select(strExpr);
   if  (curUser.Length  >0  )
{
    string myTicket=(string)curUser[0]["Ticket"];
    string myUser=(string)curUser[0]["UserName"];
    string myName=(string)curUser[0]["TrueName"];
    string myRoleID=(string)curUser[0]["RoleID"];   
    DateTime myActiveTime=(DateTime)curUser[0]["ActiveTime"];
    DateTime myRefreshtime=(DateTime)curUser[0]["RefreshTime"];
    string myClientIP =(string)curUser[0]["ClientIP"];
    myuser=new ActiveUser(myTicket,myUser,myName,myRoleID,myActiveTime,myRefreshtime,myClientIP); 
   }
   else
   {
    myuser=new ActiveUser("","","","","");   
   }
   return  myuser;
  }

/// <summary>
  ///按Ticket获取活动用户。
  /// </summary>
  public ActiveUser SingleUser_byTicket(string strTicket)
  {
   return this.SingleUser(strTicket,true);
  }

/// <summary>
  ///按UserName获取活动用户。
  /// </summary>
  public ActiveUser SingleUser_byUserName(string strUserName)
  {
   return this.SingleUser(strUserName,false);
  }

/// <summary>
  ///按Ticket判断用户是否在线。
  /// </summary>
  public bool IsOnline_byTicket(string strTicket)
  {
   return (bool)(this.SingleUser(strTicket,true).UserName!="");
  }

/// <summary>
  ///按UserName判断用户是否在线。
  /// </summary>
  public bool IsOnline_byUserName(string strUserName)
  {
   return (bool)(this.SingleUser(strUserName,false).UserName!="");
  }
}

3、 新建一个继承自PlaceHolder名为Refresh的类,执行更新自动刷新时间操作。

/// <summary>
 /// Refresh 执行更新自动刷新时间操作。
 /// </summary>
 public class Refresh: PlaceHolder
 {
  /// <summary>
  /// 设置存储Ticket的Session名称,默认为Ticket。
  /// </summary>
  public virtual string SessionName
  {
   get{
    object obj1 = this.ViewState["SessionName"];
    if (obj1 != null){ return ((string) obj1).Trim(); }
    return "Ticket";
   }
   set{
    this.ViewState["SessionName"] = value;
   }
  }

protected override void Render(HtmlTextWriter writer)
  {
   string myTicket=(string)this.Page.Session[this.SessionName];
   if(myTicket!=null)
   {  
    PassPort myPass = new PassPort();
    myPass.RefreshTime(myTicket);
    writer.Write("OK:"+DateTime.Now.ToString());
   }
   else{
    writer.Write("Sorry:"+DateTime.Now.ToString());
   }
   base.Render(writer);
 }
}

4、 新建一个继承自PlaceHolder名为Script的类,生成执行xmlhttp的js脚本。。

/// <summary>
 /// Script 生成执行xmlhttp的js脚本。
 /// </summary>
 public class Script: PlaceHolder
 {
  /// <summary>
  /// 设置js自动刷新的间隔时间,默认为25秒。
  /// </summary>
  public virtual int RefreshTime
  {
   get
   {
    object obj1 = this.ViewState["RefreshTime"];
    if (obj1 != null){return int.Parse(((string) obj1).Trim());}
    return 25;
   }
   set
   {   
    this.ViewState["RefreshTime"] = value;
   }
  }

protected override void Render(HtmlTextWriter writer)
  {
   //从web.config中读取xmlhttp的访问地址
   string refreshUrl=(string)ConfigurationSettings.AppSettings["refreshUrl"];
   string scriptString = @" <script language=""JavaScript"">"+writer.NewLine;
   scriptString += @"  window.attachEvent(""onload"", "+this.ClientID+@"_postRefresh);"+writer.NewLine;
   scriptString += @"  var "+this.ClientID+@"_xmlhttp=null;"+writer.NewLine;
   scriptString += @"  function "+this.ClientID+@"_postRefresh(){"+writer.NewLine;
   scriptString += @"   var "+this.ClientID+@"_xmlhttp = new ActiveXObject(""Msxml2.XMLHTTP"");"+writer.NewLine;
   scriptString += @"   "+this.ClientID+@"_xmlhttp.Open(""POST"", """+refreshUrl+@""", false);"+writer.NewLine;
   scriptString += @"   "+this.ClientID+@"_xmlhttp.Send();"+writer.NewLine;
   scriptString += @"   var refreshStr= "+this.ClientID+@"_xmlhttp.responseText;"+writer.NewLine;
   
   scriptString += @"   try {"+writer.NewLine;
   scriptString += @"    var refreshStr2=refreshStr;"+writer.NewLine;
   //scriptString += @"    alert(refreshStr2);"+writer.NewLine;
   scriptString += @"   }"+writer.NewLine;
   scriptString += @"   catch(e) {}"+writer.NewLine;
   scriptString += @"   setTimeout("""+this.ClientID+@"_postRefresh()"","+this.RefreshTime.ToString()+@"000);"+writer.NewLine;
   scriptString += @"  }"+writer.NewLine;
   scriptString += @"<";
   scriptString += @"/";
   scriptString += @"script>"+writer.NewLine;

writer.Write(writer.NewLine);
   writer.Write(scriptString);
   writer.Write(writer.NewLine);
   base.Render(writer);
  }
 }

注意以上四个类同属于一个名为OnlineUser的工程,他们的命名空间为OnlineUser,编译生成一个dll。

===============================================

下面我简单介绍一下调用方法:

1、 新建一个名为OnlineUserDemo的asp.net web应用程序
2、 在vs的工具箱选项卡上右击,选择[添加/移除项],浏览定位到OnlineUser.dll,确定即可把Refresh 和Script添加到工具箱。
3、 把自动生成的WebForm1.aspx删除,并设置web.config
<appSettings>
   <add key="ActiveTimeout" value="30" />
   <add key="RefreshTimeout" value="1" />
   <add key="refreshUrl" value="refresh.aspx" />
 </appSettings>
4、 添加一个名为Online.aspx的web窗体,给该窗体添加一个Script控件,一个DataGrid控件(id为DataGrid1),两个HyperLink控件(分别链接到login.aspx和logout.aspx,text属性分别设置为“登陆”和“注销”),调整好四个控件的位置,转到codebehind,在Page_Load中加入如下代码:
string myTicket=(string)this.Page.Session["Ticket"];
   if(myTicket!=null)
   {
    OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
    if(myPassPort.IsOnline_byTicket(this.Session["Ticket"].ToString()))
    {
     myPassPort.ActiveTime(this.Session["Ticket"].ToString());
     DataGrid1.DataSource=myPassPort.ActiveUsers;
     DataGrid1.DataBind();
    }
    else{
     //若在线用户列表中找不到当前用户,则定向到注销页面
     Response.Redirect("Logout.aspx");
    }
   }
   else{
    Response.Redirect("Login.aspx");
   }
5、 添加一个名为login.aspx的web窗体,给该窗体添加一个label控件(id为Label1),设置text属性为“输入一个用户名”,再添加一个textbox控件(id为TextBox1)和一个button控件(id为Button1),调整好他们的位置,双击Button1控件转到codebehind,为Button1的Click事件加入如下代码:
if(TextBox1.Text.Trim()=="")
   {
    //不能为空
    String scriptString = @"<script language=JavaScript>";
    scriptString += @"alert(""输入一个用户名\n"");";
    scriptString += @"history.go(-1);";
    scriptString += @"<";
    scriptString += @"/";
    scriptString += @"script>";
    if(!this.Page.IsStartupScriptRegistered("Startup"))
     this.Page.RegisterStartupScript("Startup", scriptString);
   }
   else{
    OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
    string myTicket=DateTime.Now.ToString("yyyyMMddHHmmss");
    string myUser=TextBox1.Text.Trim();
    string myClintIP=this.Request.UserHostAddress;
    this.Session["Ticket"]=myTicket;
    OnlineUser.ActiveUser myActiveUser=new OnlineUser.ActiveUser(myTicket,myUser,myUser,"test",myClintIP);
    myPassPort.Login(myActiveUser,true);
    Response.Redirect("Online.aspx");
   }
6、 添加一个名为logout.aspx的web窗体,给该窗体添加一个HyperLink控件,指向login.aspx,text属性设置为“重登陆”转到codebehind,在Page_Load中加入如下代码:
OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
  myPassPort.Logout(this.Session["Ticket"].ToString());
 this.Session["Ticket"]="";

7、 添加一个名为Refresh.txt的文本文件,设置其内容为:
<%@ Register TagPrefix="cc2" Namespace="OnlineUser" Assembly="OnlineUser" %>
<%@ Page %>
<cc2:Refresh id="myRefresh" runat="server"></cc2:Refresh>
把Refresh.txt改名为Refresh.aspx

8、 编译生成工程。

===============================================

下面进行功能测试:

1、 打开浏览器,在地址栏输入
http://你机器的IP地址/onlineuserdemo/Login.aspx
2、 输入一个用户名(假设是test1)登陆,自动转到online.aspx页面
3、 找同网段的另外一台机器(设你的机器为a,这台机器为b),重复执行第一步。
4、 输入一个用户名(假设是test2)登陆,自动转到online.aspx页面
5、 在b机器不断刷新online.aspx,若发现test1用户RefreshTime每过25秒自动更新一次而ActiveTime不变(这个时候a机器不要刷新页面啊),则证明a机器的自动刷新生效。
6、 在a机器不断刷新online.aspx,若发现test2用户RefreshTime每过25秒自动更新一次而ActiveTime不变(这个时候b机器不要刷新页面啊),则证明b机器的自动刷新生效。
7、 直接关闭一台机器(假设是a)上的online.aspx浏览窗口,在另一台机器(就是b啦)上刷新online.aspx,若发现1分钟后test1掉线在线用户只剩下test2,证明通过_refreshTimeout清除在线用户成功。
8、 若5、6、7三步正常,则大功告成,否则就再调试调试~~

==========================================================

转载于:https://www.cnblogs.com/China-Dragon/archive/2009/12/04/1617273.html

ASP.NET在线用户列表精确版——解决用户意外退出在线列表无法及时更新问题相关推荐

  1. 解决用户意外退出在线列表无法及时更新问题2(转载)

    1 一般来说,用户离开系统的方式有三种:主动注销.会话超时.直接关闭浏览器,对于前两种,我们很容易便可将该用户从在线列表中清除,关键是第三种(很多用户都是直接关闭窗口的~~郁闷ing),程序无法捕获窗 ...

  2. uniapp 小程序video列表页 解决 ios 详情页返回列表页,列表页重载问题。

    安卓手机不用考虑,但是在ios系统,会出现详情返回列表,列表页会重载0 首先官方推荐video 列表不超过3个video组件,再次我们用image代替video,点击是在切换出video插件. ` / ...

  3. asp.net登陆数据库的错误解决

    asp.net登陆数据库的错误解决 用户 'abc\ASPNET' 登录失败. > 说明: 执行当前 Web 请求期间,出现未处理的异常.请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的 ...

  4. mysql基础+用户管理+完增日志备份+innobackupex在线全增备份

    官网:http://dev.mysql.com/downloads/mysql 配置mysql数据库: 下载源码包解压:切换到目录下 yum -y install mysql-community-*. ...

  5. java 登录下线_java web中Listener的实现(实现用户在线统计数量和强制用户下线)...

    一.实现的具体功能 (1)实现用户在线数量的统计,并显示在页面上. (2)实现强制用户下线的功能.创建一个超链接,用于实现强制用户下线的功能. (3)监听session的创建和销毁. 二.代码实现 ( ...

  6. 局域共享解决——用户账户限制,可能的原因包括不允许空密码,登录时间限制...

    http://blog.163.com/lenovo_gxr/blog/static/5527719720145583144126/ 在客户端访问共享遇到的错误之八: 错误提示框为如图:用户账户限制, ...

  7. linux编程参数列表,Linux编程 14 文件权限(用户列表passwd,用户控制shadow,useradd模板与useradd命令参数介绍)...

    一. 概述 linux安全系统的核心是用户账户. 创建用户时会分配用户ID(UID). UID是唯一的,但在登录系统时不是用UID,而是用登录名.在讲文件权限之之前,先了解下linux是怎样处理用户账 ...

  8. ASP.NET MVC3书店--第七节 用户及身份验证(转)

    http://blog.sina.com.cn/s/blog_6ad539a90100r7jx.html 现在的问题是任何用户都可以通过StoreManager控制器来访问我们的书籍管理页面.接下来让 ...

  9. 华为云数据库推出表数据快速恢复,细粒度解决用户误删数据问题

    近来,数据库安全问题频发,如何保障业务数据可靠.可用.可恢复性再次被业界提上议程.华为云数据库也适时推出表数据快速恢复功能,支持表级别的时间点数据恢复,帮助用户快速恢复 MySQL 数据库中某张表的数 ...

最新文章

  1. rxswift中hud_如何在RxSwift中运行测试
  2. java包名命名规范[【转】
  3. Xap packaging failed , Object reference not set to an instance of an object.
  4. java numberformat异常_Java NumberFormat格式化float类型的bug
  5. JavaScript 中的相等检测
  6. Android 多模块项目入门
  7. matlab 向量法建数组(推荐)
  8. 【Java】 归并排序的非递归实现
  9. 聚合函数和group by
  10. mysql的db.opt文件_MySQL数据库的db.opt文件
  11. mysql-connector-java-8.0.26.jar MySQLJDBC下载
  12. Ubuntu 印象笔记网页剪切插件无法登录问题的一种解决方案
  13. html如何插入动图,动态图片如何插入到PPT里?
  14. 把系统桌面设置到D盘
  15. Http请求常见Header
  16. .so has text relocations. This is wasting memory and prevents security hardening. Please fix.
  17. oracle 系统资源正忙,oracle提示资源正忙怎么解决?oracle资源正忙解决方法
  18. 从零配置专属neovim - 1.配置设计概述
  19. [EKL-踩坑记] —— ES虚拟内存设置
  20. 如何使用excel画甘特图

热门文章

  1. AngularJS基于MVC的复杂操作案例
  2. oracle用户名无法登陆,sysdba却可以登陆
  3. Qt for Python Mac下使用 fbs 打包软件
  4. 修改Xmodem/Zmodem上传下载路径
  5. 线性表应用之线性表算法设计六大经典案例
  6. 浅谈三个星期零基础入门学习Thinkphp5开发restful-api接口的心得和总结
  7. 算法练习day12——190331(哈希函数、哈希表、布隆过滤器、一致性哈希)
  8. php zend gua,PHP安装使用Zend Opcache扩展
  9. cfree运行程序错误的原因_ARM Cortex-M 系列 MCU错误代码自动追踪库的使用分享
  10. layui的css文件自己写的吗_安装教程和自己的使用tips