最近所做的一个项目需要用到的在线用户列表上网搜索了一下发现现有的解决方案对用户意外退出的处理均不是太理想一般来说用户离开系统的方式有三种 主动注销会话超时直接关闭浏览器对于前两种我们很容易便可将该用户从在线列表中清除关键是第三种(很多用户都是直接关闭窗口的~~郁闷 ing)程序无法捕获窗口关闭的精确时间只能等到会话超时后在能将该用户清除出在线列表假设我们设置会话超时时间为分钟而用户登陆系统随便浏 览一个页面就以关闭浏览器的方式退出的话我们要在将近小时后才能从在线列表中将该用户清除出去(想象一下系统显示n多人在线可能除了你之外其他的 n人都关机走人了汗一个先```)而本文将尝试寻找一个解决方案把这种尴尬降至最低
我的大概思路是给每在线用户增加一个RefreshTime属性建立一个负责将当前用户的RefreshTime属性设置为当前时间的单独页面 (Refreshaspx)然后在系统的主要页面(也可以是所有页面)中通过xmlx页面一旦用户关闭了 与本系统相关的所有窗口即以直接关闭浏览器的方式退出系统那么该用户的RefreshTime属性便不会自动更新了我们再设置一个自动刷新的超时时 间(这个要比会话超时短很多_refreshTimeout)当发现某用户超过_refreshTimeout的时间没有自动刷新就能判定该用户已经 以直接关闭浏览器的方式退出了
假设我们设置会话超时时间为分钟自动刷新超时时间为分钟在客户端通过xmlx超时个人感觉不一定正确)访问一次Refreshaspx页面在用户登陆用户注销检测用户是否在线的时候都执行清理 超时用户(包括会话超时和自动刷新超时)操作这样一来在线用户列表的统计误差就由分钟降至分钟了
==========================================
具体实现如下
新建一个名为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 Ticketstring UserNamestring TrueNamestring RoleIDstring ClientIP) {
this_ticket=Ticket;
this_username=UserName;
this_truename=TrueName;
this_roleid=RoleID;
this_refreshtime=DateTimeNow;
this_activetime=DateTimeNow;
this_clientip=ClientIP;
}
public ActiveUser(string Ticketstring UserNamestring TrueNamestring RoleIDDateTime RefreshTimeDateTime ActiveTimestring 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;} }
}
新建一个名为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;
SystemType mystringtype;
mystringtype = SystemTypeGetType(SystemString);
SystemType mytimetype;
mytimetype = SystemTypeGetType(SystemDateTime);
myDataColumn = new DataColumn(Ticketmystringtype);
_activeusersColumnsAdd(myDataColumn);
myDataColumn = new DataColumn(UserNamemystringtype);
_activeusersColumnsAdd(myDataColumn);
myDataColumn = new DataColumn(TrueNamemystringtype);
_activeusersColumnsAdd(myDataColumn);
myDataColumn = new DataColumn(RoleIDmystringtype);
_activeusersColumnsAdd(myDataColumn);
myDataColumn = new DataColumn(RefreshTimemytimetype);
_activeusersColumnsAdd(myDataColumn);
myDataColumn = new DataColumn(ActiveTimemytimetype);
_activeusersColumnsAdd(myDataColumn);
myDataColumn = new DataColumn(ClientIPmystringtype);
_activeusersColumnsAdd(myDataColumn);
}
}
public PassPort()
{
userstableFormat(); //初始化在线用户表
//活动超时时间初始化 单位分钟
try { _activeTimeout=intParse(ConfigurationSettingsAppSettings[ActiveTimeout]); }
catch{ _activeTimeout=; }
//自动刷新超时时间初始化 单位分钟
try { _refreshTimeout=intParse(ConfigurationSettingsAppSettings[RefreshTimeout]); }
catch{ _refreshTimeout=; }
}
//全部用户列表
public DataTable ActiveUsers
{
get{return _activeusersCopy();}
}
/// <summary>
/// 新用户登陆
/// </summary>
public void Login(ActiveUser userbool SingleLogin)
{
DelTimeOut(); //清除超时用户
if(SingleLogin){
//若是单人登陆则注销原来登陆的用户
thisLogout(userUserNamefalse);
}
DataRow myRow;
try
{
myRow = _activeusersNewRow();
myRow[Ticket] = userTicketTrim();
myRow[UserName] = userUserNameTrim();
myRow[TrueName] = +userTrueNameTrim();
myRow[RoleID] = +userRoleIDTrim();
myRow[ActiveTime] = DateTimeNow;
myRow[RefreshTime] = DateTimeNow;
myRow[ClientIP] = userClientIPTrim();
_activeusersRowsAdd(myRow);
}
catch(Exception e)
{
throw(new Exception(eMessage));
}
_activeusersAcceptChanges();
}
/// <summary>
///用户注销根据Ticket或UserName
/// </summary>
private void Logout(string strUserKeybool byTicket)
{
DelTimeOut(); //清除超时用户
strUserKey=strUserKeyTrim();
string strExpr;
strExpr =byTicket ? Ticket= + strUserKey + : UserName= + strUserKey + ;
DataRow[] curUser;
curUser = _activeusersSelect(strExpr);
if (curUserLength > )
{
for(int i = ; i < curUserLength; i ++)
{
curUser[i]Delete();
}
}
_activeusersAcceptChanges();
}
/// <summary>
///用户注销根据Ticket
/// </summary>
/// <param name=strTicket>要注销的用户Ticket</param>
public void Logout(string strTicket){
thisLogout(strTickettrue);
}
/// <summary>
///清除超时用户
/// </summary>
private bool DelTimeOut()
{
string strExpr;
strExpr = ActiveTime < + DateTimeNowAddMinutes( _activeTimeout) + or RefreshTime < +DateTimeNowAddMinutes( _refreshTimeout)+;
DataRow[] curUser;
curUser = _activeusersSelect(strExpr);
if (curUserLength > )
{
for(int i = ; i < curUserLength; i ++)
{
curUser[i]Delete();
}
}
_activeusersAcceptChanges();
return true;
}
/// <summary>
///更新用户活动时间
/// </summary>
public void ActiveTime(string strTicket)
{
DelTimeOut();
string strExpr;
strExpr = Ticket= + strTicket + ;
DataRow[] curUser;
curUser = _activeusersSelect(strExpr);
if (curUserLength > )
{
for(int i = ; i < curUserLength; i ++)
{
curUser[i][ActiveTime]=DateTimeNow;
curUser[i][RefreshTime]=DateTimeNow;
}
}
_activeusersAcceptChanges();
}
/// <summary>
///更新系统自动刷新时间
/// </summary>
public void RefreshTime(string strTicket)
{
DelTimeOut();
string strExpr;
strExpr = Ticket= + strTicket + ;
DataRow[] curUser;
curUser = _activeusersSelect(strExpr);
if (curUserLength > )
{
for(int i = ; i < curUserLength; i ++)
{
curUser[i][RefreshTime]=DateTimeNow;
}
}
_activeusersAcceptChanges();
}
private ActiveUser SingleUser(string strUserKeybool byTicket)
{
strUserKey=strUserKeyTrim();
string strExpr;
ActiveUser myuser;
strExpr =byTicket ? Ticket= + strUserKey + : UserName= + strUserKey + ;
DataRow[] curUser;
curUser = _activeusersSelect(strExpr);
if (curUserLength > )
{
string myTicket=(string)curUser[][Ticket];
string myUser=(string)curUser[][UserName];
string myName=(string)curUser[][TrueName];
string myRoleID=(string)curUser[][RoleID];
DateTime myActiveTime=(DateTime)curUser[][ActiveTime];
DateTime myRefreshtime=(DateTime)curUser[][RefreshTime];
string myClientIP =(string)curUser[][ClientIP];
myuser=new ActiveUser(myTicketmyUsermyNamemyRoleIDmyActiveTimemyRefreshtimemyClientIP);
}
else
{
myuser=new ActiveUser();
}
return myuser;
}
/// <summary>
///按Ticket获取活动用户
/// </summary>
public ActiveUser SingleUser_byTicket(string strTicket)
{
return thisSingleUser(strTickettrue);
}
/// <summary>
///按UserName获取活动用户
/// </summary>
public ActiveUser SingleUser_byUserName(string strUserName)
{
return thisSingleUser(strUserNamefalse);
}
/// <summary>
///按Ticket判断用户是否在线
/// </summary>
public bool IsOnline_byTicket(string strTicket)
{
return (bool)(thisSingleUser(strTickettrue)UserName!=);
}
/// <summary>
///按UserName判断用户是否在线
/// </summary>
public bool IsOnline_byUserName(string strUserName)
{
return (bool)(thisSingleUser(strUserNamefalse)UserName!=);
}
}
新建一个继承自PlaceHolder名为Refresh的类执行更新自动刷新时间操作
/// <summary>
/// Refresh 执行更新自动刷新时间操作
/// </summary>
public class Refresh: PlaceHolder
{
/// <summary>
/// 设置存储Ticket的Session名称默认为Ticket
/// </summary>
public virtual string SessionName
{
get{
object obj = thisViewState[SessionName];
if (obj != null){ return ((string) obj)Trim(); }
return Ticket;
}
set{
thisViewState[SessionName] = value;
}
}
protected override void Render(HtmlTextWriter writer)
{
string myTicket=(string)thisPageSession[thisSessionName];
if(myTicket!=null)
{
PassPort myPass = new PassPort();
myPassRefreshTime(myTicket);
writerWrite(OK+DateTimeNowToString());
}
else{
writerWrite(Sorry+DateTimeNowToString());
}
baseRender(writer);
}
}
新建一个继承自PlaceHolder名为Script的类生成执行xmlhttp的js脚本
/// <summary>
/// Script 生成执行xmlhttp的js脚本
/// </summary>
public class Script: PlaceHolder
{
/// <summary>
/// 设置js自动刷新的间隔时间默认为秒
/// </summary>
public virtual int RefreshTime
{
get
{
object obj = thisViewState[RefreshTime];
if (obj != null){return intParse(((string) obj)Trim());}
return ;
}
set
{
thisViewState[RefreshTime] = value;
}
}
protected override void Render(HtmlTextWriter writer)
{
//从nfig中读取xmlhttp的访问地址
string refreshUrl=(string)ConfigurationSettingsAppSettings[refreshUrl];
string scriptString = @ <script language=JavaScript>+writerNewLine;
scriptString += @ windowattachEvent(onload +thisClientID+@_postRefresh);+writerNewLine;
scriptString += @ var +thisClientID+@_xmlhttp=null;+writerNewLine;
scriptString += @ function +thisClientID+@_postRefresh(){+writerNewLine;
scriptString += @ var +thisClientID+@_xmlhttp = new ActiveXObject(MsxmlXMLHTTP);+writerNewLine;
scriptString += @ +thisClientID+@_xmlhttpOpen(POST +refreshUrl+@ false);+writerNewLine;
scriptString += @ +thisClientID+@_xmlhttpSend();+writerNewLine;
scriptString += @ var refreshStr= +thisClientID+@_xmlhttpresponseText;+writerNewLine;
scriptString += @ try {+writerNewLine;
scriptString += @ var refreshStr=refreshStr;+writerNewLine;
//scriptString += @ alert(refreshStr);+writerNewLine;
scriptString += @ }+writerNewLine;
scriptString += @ catch(e) {}+writerNewLine;
scriptString += @ setTimeout(+thisClientID+@_postRefresh()+thisRefreshTimeToString()+@);+writerNewLine;
scriptString += @ }+writerNewLine;
scriptString += @<;
scriptString += @/;
scriptString += @script>+writerNewLine;
writerWrite(writerNewLine);
writerWrite(scriptString);
writerWrite(writerNewLine);
baseRender(writer);
}
}
注意以上四个类同属于一个名为OnlineUser的工程他们的命名空间为OnlineUser编译生成一个dll
下面我简单介绍一下调用方法
新建一个名为OnlineUserDemo的 web应用程序
在vs的工具箱选项卡上右击选择[添加/移除项]浏览定位到OnlineUserdll确定即可把Refresh 和Script添加到工具箱
把自动生成的WebFormaspx删除并设置nfig
<appSettings>
<add key=ActiveTimeout value= />
<add key=RefreshTimeout value= />
<add key=refreshUrl value=refreshaspx />
</appSettings>
添加一个名为Onlineaspx的web窗体给该窗体添加一个Script控件一个DataGrid控件(id为DataGrid)两个 HyperLink控件(分别链接到loginaspx和logoutaspxtext属性分别设置为登陆和注销)调整好四个控件的位 置转到codebehind在Page_Load中加入如下代码
string myTicket=(string)thisPageSession[Ticket];
if(myTicket!=null)
{
OnlineUserPassPort myPassPort= new OnlineUserPassPort();
if(myPassPortIsOnline_byTicket(thisSession[Ticket]ToString()))
{
myPassPortActiveTime(thisSession[Ticket]ToString());
DataGridDataSource=myPassPortActiveUsers;
DataGridDataBind();
}
else{
//若在线用户列表中找不到当前用户则定向到注销页面
ResponseRedirect(Logoutaspx);
}
}
else{
ResponseRedirect(Loginaspx);
}
添加一个名为loginaspx的web窗体给该窗体添加一个label控件(id为Label)设置text属性为输入一个用户名再添加 一个textbox控件(id为TextBox)和一个button控件(id为Button)调整好他们的位置双击Button控件转到 codebehind为Button的Click事件加入如下代码
if(TextBoxTextTrim()==)
{
//不能为空
String scriptString = @<script language=JavaScript>;
scriptString += @alert(输入一个用户名\n);;
scriptString += @historygo();;
scriptString += @<;
scriptString += @/;
scriptString += @script>;
if(!thisPageIsStartupScriptRegistered(Startup))
thisPageRegisterStartupScript(Startup scriptString);
}
else{
OnlineUserPassPort myPassPort= new OnlineUserPassPort();
string myTicket=DateTimeNowToString(yyyyMMddHHmmss);
string myUser=TextBoxTextTrim();
string myClintIP=thisRequestUserHostAddress;
thisSession[Ticket]=myTicket;
OnlineUserActiveUser myActiveUser=new OnlineUserActiveUser(myTicketmyUsermyUsertestmyClintIP);
myPassPortLogin(myActiveUsertrue);
ResponseRedirect(Onlineaspx);
}
添加一个名为logoutaspx的web窗体给该窗体添加一个HyperLink控件指向loginaspxtext属性设置为重登陆转到codebehind在Page_Load中加入如下代码
OnlineUserPassPort myPassPort= new OnlineUserPassPort();
myPassPortLogout(thisSession[Ticket]ToString());
thisSession[Ticket]=;
添加一个名为Refreshtxt的文本文件设置其内容为
<%@ Register TagPrefix=cc Namespace=OnlineUser Assembly=OnlineUser %>
<%@ Page %>
<cc:Refresh id=myRefresh runat=server></cc:Refresh>
把Refreshtxt改名为Refreshaspx
编译生成工程
===============================================
下面进行功能测试
打开浏览器在地址栏输入
x
输入一个用户名(假设是test)登陆自动转到onlineaspx页面
找同网段的另外一台机器(设你的机器为a这台机器为b)重复执行第一步
输入一个用户名(假设是test)登陆自动转到onlineaspx页面
在b机器不断刷新onlineaspx若发现test用户RefreshTime每过秒自动更新一次而ActiveTime不变(这个时候a机器不要刷新页面啊)则证明a机器的自动刷新生效
在a机器不断刷新onlineaspx若发现test用户RefreshTime每过秒自动更新一次而ActiveTime不变(这个时候b机器不要刷新页面啊)则证明b机器的自动刷新生效
直接关闭一台机器(假设是a)上的onlineaspx浏览窗口在另一台机器(就是b啦)上刷新onlineaspx若发现分钟后test掉线在线用户只剩下test证明通过_refreshTimeout清除在线用户成功
若三步正常则大功告成否则就再调试调试~~