1 一般来说,用户离开系统的方式有三种:主动注销、会话超时、直接关闭浏览器,对于前两种,我们很容易便可将该用户从在线列表中清除,关键是第三种(很多用户都是直接关闭窗口的~~郁闷ing),程序无法捕获窗口关闭的精确时间,只能等到会话超时后在能将该用户清除出在线列表,假设我们设置会话超时时间为60分钟,而用户登陆系统随便浏览一个页面就以关闭浏览器的方式退出的话,我们要在将近1小时后才能从在线列表中将该用户清除出去(想象一下,系统显示n多人在线,可能除了你之外其他的n-1人都关机走人了,汗一个先```),而本文将尝试寻找一个解决方案把这种尴尬降至最低。
2 我的大概思路是,给每在线用户增加一个RefreshTime属性,建立一个负责将当前用户的RefreshTime属性设置为当前时间的单独页面(Refresh.aspx),然后在系统的主要页面(也可以是所有页面)中通过xmlhttp不断地请求Refresh.aspx页面,一旦用户关闭了与本系统相关的所有窗口,即以直接关闭浏览器的方式退出系统,那么该用户的RefreshTime属性便不会自动更新了,我们再设置一个自动刷新的超时时间(这个要比会话超时短很多_refreshTimeout),当发现某用户超过_refreshTimeout的时间没有自动刷新,就能判定该用户已经以直接关闭浏览器的方式退出了。
3 假设我们设置会话超时时间为60分钟,自动刷新超时时间为1分钟,在客户端通过xmlhttp每隔25秒(之所以不设1分钟,是防止网速慢的时候访问 Refresh.aspx超时,个人感觉,不一定正确)访问一次Refresh.aspx页面,在用户登陆、用户注销、检测用户是否在线的时候都执行清理超时用户(包括会话超时和自动刷新超时)操作,这样一来,在线用户列表的统计误差就由60分钟降至1分钟了。
4
5 ==========================================
6
7
8
9 具体实现如下:
10
11
12 1、 新建一个名为ActiveUser的类,存储单个活动用户数据。
13
14 /// <summary>
15 /// 单个在线用户数据,无法继承此类。
16 /// </summary>
17 public sealed class ActiveUser
18 {
19 private readonly string _ticket; //票据名称
20 private readonly string _username; //登陆用户名
21 private readonly string _truename; //登陆用户名
22 private readonly string _roleid; //角色
23 private readonly DateTime _refreshtime; //最新刷新时间
24 private readonly DateTime _activetime; //最新活动时间
25 private readonly string _clientip; //登陆IP
26
27 public ActiveUser(string Ticket,string UserName,string TrueName,string RoleID,string ClientIP) {
28 this._ticket=Ticket;
29 this._username=UserName;
30 this._truename=TrueName;
31 this._roleid=RoleID;
32 this._refreshtime=DateTime.Now;
33 this._activetime=DateTime.Now;
34 this._clientip=ClientIP;
35 }
36
37 public ActiveUser(string Ticket,string UserName,string TrueName,string RoleID,DateTime RefreshTime,DateTime ActiveTime,string ClientIP) {
38 this._ticket=Ticket;
39 this._username=UserName;
40 this._truename=TrueName;
41 this._roleid=RoleID;
42 this._refreshtime=RefreshTime;
43 this._activetime=ActiveTime;
44 this._clientip=ClientIP;
45 }
46
47 public string Ticket { get{return _ticket;} }
48 public string UserName { get{return _username;} }
49 public string TrueName { get{return _truename;} }
50 public string RoleID { get{return _roleid;} }
51 public DateTime RefreshTime { get{return _refreshtime;} }
52 public DateTime ActiveTime { get{return _activetime;} }
53 public string ClientIP { get{return _clientip;} }
54
55 }
56
57
58
59
60 2、 新建一个名为PassPort的类,存储在线用户列表。
61
62 /// <summary>
63 /// PassPort 存储在线用户列表。
64 /// </summary>
65 public class PassPort
66 {
67 private static DataTable _activeusers;
68 private int _activeTimeout;
69 private int _refreshTimeout;
70
71 /// <summary>
72 /// 初始化在线用户表。
73 /// </summary>
74 private void userstableFormat()
75 {
76 if(_activeusers==null) {
77 _activeusers = new DataTable("ActiveUsers");
78 DataColumn myDataColumn;
79 System.Type mystringtype;
80 mystringtype = System.Type.GetType("System.String");
81 System.Type mytimetype;
82 mytimetype = System.Type.GetType("System.DateTime");
83 myDataColumn = new DataColumn("Ticket",mystringtype);
84 _activeusers.Columns.Add(myDataColumn);
85 myDataColumn = new DataColumn("UserName",mystringtype);
86 _activeusers.Columns.Add(myDataColumn);
87 myDataColumn = new DataColumn("TrueName",mystringtype);
88 _activeusers.Columns.Add(myDataColumn);
89 myDataColumn = new DataColumn("RoleID",mystringtype);
90 _activeusers.Columns.Add(myDataColumn);
91 myDataColumn = new DataColumn("RefreshTime",mytimetype);
92 _activeusers.Columns.Add(myDataColumn);
93 myDataColumn = new DataColumn("ActiveTime",mytimetype);
94 _activeusers.Columns.Add(myDataColumn);
95 myDataColumn = new DataColumn("ClientIP",mystringtype);
96 _activeusers.Columns.Add(myDataColumn);
97 }
98 }
99
100 public PassPort()
101 {
102 userstableFormat(); //初始化在线用户表
103 //活动超时时间初始化 单位:分钟
104 try { _activeTimeout=int.Parse(ConfigurationSettings.AppSettings["ActiveTimeout"]); }
105 catch{ _activeTimeout=60; }
106 //自动刷新超时时间初始化 单位:分钟
107 try { _refreshTimeout=int.Parse(ConfigurationSettings.AppSettings["RefreshTimeout"]); }
108 catch{ _refreshTimeout=1; }
109 }
110
111 //全部用户列表
112 public DataTable ActiveUsers
113 {
114 get{return _activeusers.Copy();}
115 }
116
117 /// <summary>
118 /// 新用户登陆。
119 /// </summary>
120 public void Login(ActiveUser user,bool SingleLogin)
121 {
122 DelTimeOut(); //清除超时用户
123 if(SingleLogin){
124 //若是单人登陆则注销原来登陆的用户
125 this.Logout(user.UserName,false);
126 }
127 DataRow myRow;
128 try
129 {
130 myRow = _activeusers.NewRow();
131 myRow["Ticket"] = user.Ticket.Trim();
132 myRow["UserName"] = user.UserName.Trim();
133 myRow["TrueName"] = ""+user.TrueName.Trim();
134 myRow["RoleID"] = ""+user.RoleID.Trim();
135 myRow["ActiveTime"] = DateTime.Now;
136 myRow["RefreshTime"] = DateTime.Now;
137 myRow["ClientIP"] = user.ClientIP.Trim();
138 _activeusers.Rows.Add(myRow);
139 }
140 catch(Exception e)
141 {
142 throw(new Exception(e.Message));
143 }
144 _activeusers.AcceptChanges();
145
146 }
147
148 /// <summary>
149 ///用户注销,根据Ticket或UserName。
150 /// </summary>
151 private void Logout(string strUserKey,bool byTicket)
152 {
153 DelTimeOut(); //清除超时用户
154 strUserKey=strUserKey.Trim();
155 string strExpr;
156 strExpr =byTicket ? "Ticket='" + strUserKey +"'" : "UserName='" + strUserKey + "'";
157 DataRow[] curUser;
158 curUser = _activeusers.Select(strExpr);
159 if (curUser.Length >0 )
160 {
161 for(int i = 0; i < curUser.Length; i ++)
162 {
163 curUser[i].Delete();
164 }
165 }
166 _activeusers.AcceptChanges();
167 }
168
169 /// <summary>
170 ///用户注销,根据Ticket。
171 /// </summary>
172 /// <param name="strTicket">要注销的用户Ticket</param>
173 public void Logout(string strTicket){
174 this.Logout(strTicket,true);
175 }
176
177 /// <summary>
178 ///清除超时用户。
179 /// </summary>
180 private bool DelTimeOut()
181 {
182 string strExpr;
183 strExpr = "ActiveTime < '" + DateTime.Now.AddMinutes( 0 - _activeTimeout) + "'or RefreshTime < '"+DateTime.Now.AddMinutes( 0 - _refreshTimeout)+"'";
184 DataRow[] curUser;
185 curUser = _activeusers.Select(strExpr);
186 if (curUser.Length >0 )
187 {
188 for(int i = 0; i < curUser.Length; i ++)
189 {
190 curUser[i].Delete();
191 }
192 }
193 _activeusers.AcceptChanges();
194 return true;
195 }
196
197 /// <summary>
198 ///更新用户活动时间。
199 /// </summary>
200 public void ActiveTime(string strTicket)
201 {
202 DelTimeOut();
203 string strExpr;
204 strExpr = "Ticket='" + strTicket + "'";
205 DataRow[] curUser;
206 curUser = _activeusers.Select(strExpr);
207 if (curUser.Length >0 )
208 {
209 for(int i = 0; i < curUser.Length; i ++)
210 {
211 curUser[i]["ActiveTime"]=DateTime.Now;
212 curUser[i]["RefreshTime"]=DateTime.Now;
213 }
214 }
215 _activeusers.AcceptChanges();
216 }
217
218 /// <summary>
219 ///更新系统自动刷新时间。
220 /// </summary>
221 public void RefreshTime(string strTicket)
222 {
223 DelTimeOut();
224 string strExpr;
225 strExpr = "Ticket='" + strTicket + "'";
226 DataRow[] curUser;
227 curUser = _activeusers.Select(strExpr);
228 if (curUser.Length >0 )
229 {
230 for(int i = 0; i < curUser.Length; i ++)
231 {
232 curUser[i]["RefreshTime"]=DateTime.Now;
233 }
234 }
235 _activeusers.AcceptChanges();
236 }
237
238 private ActiveUser SingleUser(string strUserKey,bool byTicket)
239 {
240 strUserKey=strUserKey.Trim();
241 string strExpr;
242 ActiveUser myuser;
243 strExpr =byTicket ? "Ticket='" + strUserKey +"'" : "UserName='" + strUserKey + "'";
244 DataRow[] curUser;
245 curUser = _activeusers.Select(strExpr);
246 if (curUser.Length >0 )
247 {
248 string myTicket=(string)curUser[0]["Ticket"];
249 string myUser=(string)curUser[0]["UserName"];
250 string myName=(string)curUser[0]["TrueName"];
251 string myRoleID=(string)curUser[0]["RoleID"];
252 DateTime myActiveTime=(DateTime)curUser[0]["ActiveTime"];
253 DateTime myRefreshtime=(DateTime)curUser[0]["RefreshTime"];
254 string myClientIP =(string)curUser[0]["ClientIP"];
255 myuser=new ActiveUser(myTicket,myUser,myName,myRoleID,myActiveTime,myRefreshtime,myClientIP);
256 }
257 else
258 {
259 myuser=new ActiveUser("","","","","");
260 }
261 return myuser;
262 }
263
264 /// <summary>
265 ///按Ticket获取活动用户。
266 /// </summary>
267 public ActiveUser SingleUser_byTicket(string strTicket)
268 {
269 return this.SingleUser(strTicket,true);
270 }
271
272 /// <summary>
273 ///按UserName获取活动用户。
274 /// </summary>
275 public ActiveUser SingleUser_byUserName(string strUserName)
276 {
277 return this.SingleUser(strUserName,false);
278 }
279
280 /// <summary>
281 ///按Ticket判断用户是否在线。
282 /// </summary>
283 public bool IsOnline_byTicket(string strTicket)
284 {
285 return (bool)(this.SingleUser(strTicket,true).UserName!="");
286 }
287
288 /// <summary>
289 ///按UserName判断用户是否在线。
290 /// </summary>
291 public bool IsOnline_byUserName(string strUserName)
292 {
293 return (bool)(this.SingleUser(strUserName,false).UserName!="");
294 }
295 }
296
297
298 3、 新建一个继承自PlaceHolder名为Refresh的类,执行更新自动刷新时间操作。
299
300
301 /// <summary>
302 /// Refresh 执行更新自动刷新时间操作。
303 /// </summary>
304 public class Refresh: PlaceHolder
305 {
306 /// <summary>
307 /// 设置存储Ticket的Session名称,默认为Ticket。
308 /// </summary>
309 public virtual string SessionName
310 {
311 get{
312 object obj1 = this.ViewState["SessionName"];
313 if (obj1 != null){ return ((string) obj1).Trim(); }
314 return "Ticket";
315 }
316 set{
317 this.ViewState["SessionName"] = value;
318 }
319 }
320
321 protected override void Render(HtmlTextWriter writer)
322 {
323 string myTicket=(string)this.Page.Session[this.SessionName];
324 if(myTicket!=null)
325 {
326 PassPort myPass = new PassPort();
327 myPass.RefreshTime(myTicket);
328 writer.Write("OK:"+DateTime.Now.ToString());
329 }
330 else{
331 writer.Write("Sorry:"+DateTime.Now.ToString());
332 }
333 base.Render(writer);
334 }
335 }
336
337 4、 新建一个继承自PlaceHolder名为Script的类,生成执行xmlhttp的js脚本。。
338
339
340 /// <summary>
341 /// Script 生成执行xmlhttp的js脚本。
342 /// </summary>
343 public class Script: PlaceHolder
344 {
345 /// <summary>
346 /// 设置js自动刷新的间隔时间,默认为25秒。
347 /// </summary>
348 public virtual int RefreshTime
349 {
350 get
351 {
352 object obj1 = this.ViewState["RefreshTime"];
353 if (obj1 != null){return int.Parse(((string) obj1).Trim());}
354 return 25;
355 }
356 set
357 {
358 this.ViewState["RefreshTime"] = value;
359 }
360 }
361
362 protected override void Render(HtmlTextWriter writer)
363 {
364 //从web.config中读取xmlhttp的访问地址
365 string refreshUrl=(string)ConfigurationSettings.AppSettings["refreshUrl"];
366 string scriptString = @" <script language=""JavaScript"">"+writer.NewLine;
367 scriptString += @" window.attachEvent(""onload"", "+this.ClientID+@"_postRefresh);"+writer.NewLine;
368 scriptString += @" var "+this.ClientID+@"_xmlhttp=null;"+writer.NewLine;
369 scriptString += @" function "+this.ClientID+@"_postRefresh(){"+writer.NewLine;
370 scriptString += @" var "+this.ClientID+@"_xmlhttp = new ActiveXObject(""Msxml2.XMLHTTP"");"+writer.NewLine;
371 scriptString += @" "+this.ClientID+@"_xmlhttp.Open(""POST"", """+refreshUrl+@""", false);"+writer.NewLine;
372 scriptString += @" "+this.ClientID+@"_xmlhttp.Send();"+writer.NewLine;
373 scriptString += @" var refreshStr= "+this.ClientID+@"_xmlhttp.responseText;"+writer.NewLine;
374
375 scriptString += @" try {"+writer.NewLine;
376 scriptString += @" var refreshStr2=refreshStr;"+writer.NewLine;
377 //scriptString += @" alert(refreshStr2);"+writer.NewLine;
378 scriptString += @" }"+writer.NewLine;
379 scriptString += @" catch(e) {}"+writer.NewLine;
380 scriptString += @" setTimeout("""+this.ClientID+@"_postRefresh()"","+this.RefreshTime.ToString()+@"000);"+writer.NewLine;
381 scriptString += @" }"+writer.NewLine;
382 scriptString += @"<";
383 scriptString += @"/";
384 scriptString += @"script>"+writer.NewLine;
385
386 writer.Write(writer.NewLine);
387 writer.Write(scriptString);
388 writer.Write(writer.NewLine);
389 base.Render(writer);
390 }
391 }
392
393
394 注意以上四个类同属于一个名为OnlineUser的工程,他们的命名空间为OnlineUser,编译生成一个dll。
395
396
397
398
399 ===============================================
400
401
402
403 下面我简单介绍一下调用方法:
404
405 1、 新建一个名为OnlineUserDemo的asp.net web应用程序
406 2、 在vs的工具箱选项卡上右击,选择[添加/移除项],浏览定位到OnlineUser.dll,确定即可把Refresh 和Script添加到工具箱。
407 3、 把自动生成的WebForm1.aspx删除,并设置web.config
408 <appSettings>
409 <add key="ActiveTimeout" value="30" />
410 <add key="RefreshTimeout" value="1" />
411 <add key="refreshUrl" value="refresh.aspx" />
412 </appSettings>
413 4、添加一个名为Online.aspx的web窗体,给该窗体添加一个Script控件,一个DataGrid控件(id为DataGrid1),两个 HyperLink控件(分别链接到login.aspx和logout.aspx,text属性分别设置为“登陆”和“注销”),调整好四个控件的位置,转到codebehind,在Page_Load中加入如下代码:
414 string myTicket=(string)this.Page.Session["Ticket"];
415 if(myTicket!=null)
416 {
417 OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
418 if(myPassPort.IsOnline_byTicket(this.Session["Ticket"].ToString()))
419 {
420 myPassPort.ActiveTime(this.Session["Ticket"].ToString());
421 DataGrid1.DataSource=myPassPort.ActiveUsers;
422 DataGrid1.DataBind();
423 }
424 else{
425 //若在线用户列表中找不到当前用户,则定向到注销页面
426 Response.Redirect("Logout.aspx");
427 }
428 }
429 else{
430 Response.Redirect("Login.aspx");
431 }
432 5、添加一个名为login.aspx的web窗体,给该窗体添加一个label控件(id为Label1),设置text属性为“输入一个用户名”,再添加一个textbox控件(id为TextBox1)和一个button控件(id为Button1),调整好他们的位置,双击Button1控件转到 codebehind,为Button1的Click事件加入如下代码:
433 if(TextBox1.Text.Trim()=="")
434 {
435 //不能为空
436 String scriptString = @"<script language=JavaScript>";
437 scriptString += @"alert(""输入一个用户名\n"");";
438 scriptString += @"history.go(-1);";
439 scriptString += @"<";
440 scriptString += @"/";
441 scriptString += @"script>";
442 if(!this.Page.IsStartupScriptRegistered("Startup"))
443 this.Page.RegisterStartupScript("Startup", scriptString);
444 }
445 else{
446 OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
447 string myTicket=DateTime.Now.ToString("yyyyMMddHHmmss");
448 string myUser=TextBox1.Text.Trim();
449 string myClintIP=this.Request.UserHostAddress;
450 this.Session["Ticket"]=myTicket;
451 OnlineUser.ActiveUser myActiveUser=new OnlineUser.ActiveUser(myTicket,myUser,myUser,"test",myClintIP);
452 myPassPort.Login(myActiveUser,true);
453 Response.Redirect("Online.aspx");
454 }
455 6、 添加一个名为logout.aspx的web窗体,给该窗体添加一个HyperLink控件,指向login.aspx,text属性设置为“重登陆”转到codebehind,在Page_Load中加入如下代码:
456 OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
457 myPassPort.Logout(this.Session["Ticket"].ToString());
458 this.Session["Ticket"]="";
459
460 7、 添加一个名为Refresh.txt的文本文件,设置其内容为:
461 <%@ Register TagPrefix="cc2" Namespace="OnlineUser" Assembly="OnlineUser" %>
462 <%@ Page %>
463 <cc2:Refresh id="myRefresh" runat="server"></cc2:Refresh>
464 把Refresh.txt改名为Refresh.aspx
465
466 8、 编译生成工程。
467
468 ===============================================
469
470
471 下面进行功能测试:
472
473 1、 打开浏览器,在地址栏输入
474 http://你机器的IP地址/onlineuserdemo/Login.aspx
475 2、 输入一个用户名(假设是test1)登陆,自动转到online.aspx页面
476 3、 找同网段的另外一台机器(设你的机器为a,这台机器为b),重复执行第一步。
477 4、 输入一个用户名(假设是test2)登陆,自动转到online.aspx页面
478 5、 在b机器不断刷新online.aspx,若发现test1用户RefreshTime每过25秒自动更新一次而ActiveTime不变(这个时候a机器不要刷新页面啊),则证明a机器的自动刷新生效。
479 6、 在a机器不断刷新online.aspx,若发现test2用户RefreshTime每过25秒自动更新一次而ActiveTime不变(这个时候b机器不要刷新页面啊),则证明b机器的自动刷新生效。
480 7、 直接关闭一台机器(假设是a)上的online.aspx浏览窗口,在另一台机器(就是b啦)上刷新online.aspx,若发现1分钟后test1掉线在线用户只剩下test2,证明通过_refreshTimeout清除在线用户成功。
481 8、 若5、6、7三步正常,则大功告成,否则就再调试调试~~
482
483
484
2 我的大概思路是,给每在线用户增加一个RefreshTime属性,建立一个负责将当前用户的RefreshTime属性设置为当前时间的单独页面(Refresh.aspx),然后在系统的主要页面(也可以是所有页面)中通过xmlhttp不断地请求Refresh.aspx页面,一旦用户关闭了与本系统相关的所有窗口,即以直接关闭浏览器的方式退出系统,那么该用户的RefreshTime属性便不会自动更新了,我们再设置一个自动刷新的超时时间(这个要比会话超时短很多_refreshTimeout),当发现某用户超过_refreshTimeout的时间没有自动刷新,就能判定该用户已经以直接关闭浏览器的方式退出了。
3 假设我们设置会话超时时间为60分钟,自动刷新超时时间为1分钟,在客户端通过xmlhttp每隔25秒(之所以不设1分钟,是防止网速慢的时候访问 Refresh.aspx超时,个人感觉,不一定正确)访问一次Refresh.aspx页面,在用户登陆、用户注销、检测用户是否在线的时候都执行清理超时用户(包括会话超时和自动刷新超时)操作,这样一来,在线用户列表的统计误差就由60分钟降至1分钟了。
4
5 ==========================================
6
7
8
9 具体实现如下:
10
11
12 1、 新建一个名为ActiveUser的类,存储单个活动用户数据。
13
14 /// <summary>
15 /// 单个在线用户数据,无法继承此类。
16 /// </summary>
17 public sealed class ActiveUser
18 {
19 private readonly string _ticket; //票据名称
20 private readonly string _username; //登陆用户名
21 private readonly string _truename; //登陆用户名
22 private readonly string _roleid; //角色
23 private readonly DateTime _refreshtime; //最新刷新时间
24 private readonly DateTime _activetime; //最新活动时间
25 private readonly string _clientip; //登陆IP
26
27 public ActiveUser(string Ticket,string UserName,string TrueName,string RoleID,string ClientIP) {
28 this._ticket=Ticket;
29 this._username=UserName;
30 this._truename=TrueName;
31 this._roleid=RoleID;
32 this._refreshtime=DateTime.Now;
33 this._activetime=DateTime.Now;
34 this._clientip=ClientIP;
35 }
36
37 public ActiveUser(string Ticket,string UserName,string TrueName,string RoleID,DateTime RefreshTime,DateTime ActiveTime,string ClientIP) {
38 this._ticket=Ticket;
39 this._username=UserName;
40 this._truename=TrueName;
41 this._roleid=RoleID;
42 this._refreshtime=RefreshTime;
43 this._activetime=ActiveTime;
44 this._clientip=ClientIP;
45 }
46
47 public string Ticket { get{return _ticket;} }
48 public string UserName { get{return _username;} }
49 public string TrueName { get{return _truename;} }
50 public string RoleID { get{return _roleid;} }
51 public DateTime RefreshTime { get{return _refreshtime;} }
52 public DateTime ActiveTime { get{return _activetime;} }
53 public string ClientIP { get{return _clientip;} }
54
55 }
56
57
58
59
60 2、 新建一个名为PassPort的类,存储在线用户列表。
61
62 /// <summary>
63 /// PassPort 存储在线用户列表。
64 /// </summary>
65 public class PassPort
66 {
67 private static DataTable _activeusers;
68 private int _activeTimeout;
69 private int _refreshTimeout;
70
71 /// <summary>
72 /// 初始化在线用户表。
73 /// </summary>
74 private void userstableFormat()
75 {
76 if(_activeusers==null) {
77 _activeusers = new DataTable("ActiveUsers");
78 DataColumn myDataColumn;
79 System.Type mystringtype;
80 mystringtype = System.Type.GetType("System.String");
81 System.Type mytimetype;
82 mytimetype = System.Type.GetType("System.DateTime");
83 myDataColumn = new DataColumn("Ticket",mystringtype);
84 _activeusers.Columns.Add(myDataColumn);
85 myDataColumn = new DataColumn("UserName",mystringtype);
86 _activeusers.Columns.Add(myDataColumn);
87 myDataColumn = new DataColumn("TrueName",mystringtype);
88 _activeusers.Columns.Add(myDataColumn);
89 myDataColumn = new DataColumn("RoleID",mystringtype);
90 _activeusers.Columns.Add(myDataColumn);
91 myDataColumn = new DataColumn("RefreshTime",mytimetype);
92 _activeusers.Columns.Add(myDataColumn);
93 myDataColumn = new DataColumn("ActiveTime",mytimetype);
94 _activeusers.Columns.Add(myDataColumn);
95 myDataColumn = new DataColumn("ClientIP",mystringtype);
96 _activeusers.Columns.Add(myDataColumn);
97 }
98 }
99
100 public PassPort()
101 {
102 userstableFormat(); //初始化在线用户表
103 //活动超时时间初始化 单位:分钟
104 try { _activeTimeout=int.Parse(ConfigurationSettings.AppSettings["ActiveTimeout"]); }
105 catch{ _activeTimeout=60; }
106 //自动刷新超时时间初始化 单位:分钟
107 try { _refreshTimeout=int.Parse(ConfigurationSettings.AppSettings["RefreshTimeout"]); }
108 catch{ _refreshTimeout=1; }
109 }
110
111 //全部用户列表
112 public DataTable ActiveUsers
113 {
114 get{return _activeusers.Copy();}
115 }
116
117 /// <summary>
118 /// 新用户登陆。
119 /// </summary>
120 public void Login(ActiveUser user,bool SingleLogin)
121 {
122 DelTimeOut(); //清除超时用户
123 if(SingleLogin){
124 //若是单人登陆则注销原来登陆的用户
125 this.Logout(user.UserName,false);
126 }
127 DataRow myRow;
128 try
129 {
130 myRow = _activeusers.NewRow();
131 myRow["Ticket"] = user.Ticket.Trim();
132 myRow["UserName"] = user.UserName.Trim();
133 myRow["TrueName"] = ""+user.TrueName.Trim();
134 myRow["RoleID"] = ""+user.RoleID.Trim();
135 myRow["ActiveTime"] = DateTime.Now;
136 myRow["RefreshTime"] = DateTime.Now;
137 myRow["ClientIP"] = user.ClientIP.Trim();
138 _activeusers.Rows.Add(myRow);
139 }
140 catch(Exception e)
141 {
142 throw(new Exception(e.Message));
143 }
144 _activeusers.AcceptChanges();
145
146 }
147
148 /// <summary>
149 ///用户注销,根据Ticket或UserName。
150 /// </summary>
151 private void Logout(string strUserKey,bool byTicket)
152 {
153 DelTimeOut(); //清除超时用户
154 strUserKey=strUserKey.Trim();
155 string strExpr;
156 strExpr =byTicket ? "Ticket='" + strUserKey +"'" : "UserName='" + strUserKey + "'";
157 DataRow[] curUser;
158 curUser = _activeusers.Select(strExpr);
159 if (curUser.Length >0 )
160 {
161 for(int i = 0; i < curUser.Length; i ++)
162 {
163 curUser[i].Delete();
164 }
165 }
166 _activeusers.AcceptChanges();
167 }
168
169 /// <summary>
170 ///用户注销,根据Ticket。
171 /// </summary>
172 /// <param name="strTicket">要注销的用户Ticket</param>
173 public void Logout(string strTicket){
174 this.Logout(strTicket,true);
175 }
176
177 /// <summary>
178 ///清除超时用户。
179 /// </summary>
180 private bool DelTimeOut()
181 {
182 string strExpr;
183 strExpr = "ActiveTime < '" + DateTime.Now.AddMinutes( 0 - _activeTimeout) + "'or RefreshTime < '"+DateTime.Now.AddMinutes( 0 - _refreshTimeout)+"'";
184 DataRow[] curUser;
185 curUser = _activeusers.Select(strExpr);
186 if (curUser.Length >0 )
187 {
188 for(int i = 0; i < curUser.Length; i ++)
189 {
190 curUser[i].Delete();
191 }
192 }
193 _activeusers.AcceptChanges();
194 return true;
195 }
196
197 /// <summary>
198 ///更新用户活动时间。
199 /// </summary>
200 public void ActiveTime(string strTicket)
201 {
202 DelTimeOut();
203 string strExpr;
204 strExpr = "Ticket='" + strTicket + "'";
205 DataRow[] curUser;
206 curUser = _activeusers.Select(strExpr);
207 if (curUser.Length >0 )
208 {
209 for(int i = 0; i < curUser.Length; i ++)
210 {
211 curUser[i]["ActiveTime"]=DateTime.Now;
212 curUser[i]["RefreshTime"]=DateTime.Now;
213 }
214 }
215 _activeusers.AcceptChanges();
216 }
217
218 /// <summary>
219 ///更新系统自动刷新时间。
220 /// </summary>
221 public void RefreshTime(string strTicket)
222 {
223 DelTimeOut();
224 string strExpr;
225 strExpr = "Ticket='" + strTicket + "'";
226 DataRow[] curUser;
227 curUser = _activeusers.Select(strExpr);
228 if (curUser.Length >0 )
229 {
230 for(int i = 0; i < curUser.Length; i ++)
231 {
232 curUser[i]["RefreshTime"]=DateTime.Now;
233 }
234 }
235 _activeusers.AcceptChanges();
236 }
237
238 private ActiveUser SingleUser(string strUserKey,bool byTicket)
239 {
240 strUserKey=strUserKey.Trim();
241 string strExpr;
242 ActiveUser myuser;
243 strExpr =byTicket ? "Ticket='" + strUserKey +"'" : "UserName='" + strUserKey + "'";
244 DataRow[] curUser;
245 curUser = _activeusers.Select(strExpr);
246 if (curUser.Length >0 )
247 {
248 string myTicket=(string)curUser[0]["Ticket"];
249 string myUser=(string)curUser[0]["UserName"];
250 string myName=(string)curUser[0]["TrueName"];
251 string myRoleID=(string)curUser[0]["RoleID"];
252 DateTime myActiveTime=(DateTime)curUser[0]["ActiveTime"];
253 DateTime myRefreshtime=(DateTime)curUser[0]["RefreshTime"];
254 string myClientIP =(string)curUser[0]["ClientIP"];
255 myuser=new ActiveUser(myTicket,myUser,myName,myRoleID,myActiveTime,myRefreshtime,myClientIP);
256 }
257 else
258 {
259 myuser=new ActiveUser("","","","","");
260 }
261 return myuser;
262 }
263
264 /// <summary>
265 ///按Ticket获取活动用户。
266 /// </summary>
267 public ActiveUser SingleUser_byTicket(string strTicket)
268 {
269 return this.SingleUser(strTicket,true);
270 }
271
272 /// <summary>
273 ///按UserName获取活动用户。
274 /// </summary>
275 public ActiveUser SingleUser_byUserName(string strUserName)
276 {
277 return this.SingleUser(strUserName,false);
278 }
279
280 /// <summary>
281 ///按Ticket判断用户是否在线。
282 /// </summary>
283 public bool IsOnline_byTicket(string strTicket)
284 {
285 return (bool)(this.SingleUser(strTicket,true).UserName!="");
286 }
287
288 /// <summary>
289 ///按UserName判断用户是否在线。
290 /// </summary>
291 public bool IsOnline_byUserName(string strUserName)
292 {
293 return (bool)(this.SingleUser(strUserName,false).UserName!="");
294 }
295 }
296
297
298 3、 新建一个继承自PlaceHolder名为Refresh的类,执行更新自动刷新时间操作。
299
300
301 /// <summary>
302 /// Refresh 执行更新自动刷新时间操作。
303 /// </summary>
304 public class Refresh: PlaceHolder
305 {
306 /// <summary>
307 /// 设置存储Ticket的Session名称,默认为Ticket。
308 /// </summary>
309 public virtual string SessionName
310 {
311 get{
312 object obj1 = this.ViewState["SessionName"];
313 if (obj1 != null){ return ((string) obj1).Trim(); }
314 return "Ticket";
315 }
316 set{
317 this.ViewState["SessionName"] = value;
318 }
319 }
320
321 protected override void Render(HtmlTextWriter writer)
322 {
323 string myTicket=(string)this.Page.Session[this.SessionName];
324 if(myTicket!=null)
325 {
326 PassPort myPass = new PassPort();
327 myPass.RefreshTime(myTicket);
328 writer.Write("OK:"+DateTime.Now.ToString());
329 }
330 else{
331 writer.Write("Sorry:"+DateTime.Now.ToString());
332 }
333 base.Render(writer);
334 }
335 }
336
337 4、 新建一个继承自PlaceHolder名为Script的类,生成执行xmlhttp的js脚本。。
338
339
340 /// <summary>
341 /// Script 生成执行xmlhttp的js脚本。
342 /// </summary>
343 public class Script: PlaceHolder
344 {
345 /// <summary>
346 /// 设置js自动刷新的间隔时间,默认为25秒。
347 /// </summary>
348 public virtual int RefreshTime
349 {
350 get
351 {
352 object obj1 = this.ViewState["RefreshTime"];
353 if (obj1 != null){return int.Parse(((string) obj1).Trim());}
354 return 25;
355 }
356 set
357 {
358 this.ViewState["RefreshTime"] = value;
359 }
360 }
361
362 protected override void Render(HtmlTextWriter writer)
363 {
364 //从web.config中读取xmlhttp的访问地址
365 string refreshUrl=(string)ConfigurationSettings.AppSettings["refreshUrl"];
366 string scriptString = @" <script language=""JavaScript"">"+writer.NewLine;
367 scriptString += @" window.attachEvent(""onload"", "+this.ClientID+@"_postRefresh);"+writer.NewLine;
368 scriptString += @" var "+this.ClientID+@"_xmlhttp=null;"+writer.NewLine;
369 scriptString += @" function "+this.ClientID+@"_postRefresh(){"+writer.NewLine;
370 scriptString += @" var "+this.ClientID+@"_xmlhttp = new ActiveXObject(""Msxml2.XMLHTTP"");"+writer.NewLine;
371 scriptString += @" "+this.ClientID+@"_xmlhttp.Open(""POST"", """+refreshUrl+@""", false);"+writer.NewLine;
372 scriptString += @" "+this.ClientID+@"_xmlhttp.Send();"+writer.NewLine;
373 scriptString += @" var refreshStr= "+this.ClientID+@"_xmlhttp.responseText;"+writer.NewLine;
374
375 scriptString += @" try {"+writer.NewLine;
376 scriptString += @" var refreshStr2=refreshStr;"+writer.NewLine;
377 //scriptString += @" alert(refreshStr2);"+writer.NewLine;
378 scriptString += @" }"+writer.NewLine;
379 scriptString += @" catch(e) {}"+writer.NewLine;
380 scriptString += @" setTimeout("""+this.ClientID+@"_postRefresh()"","+this.RefreshTime.ToString()+@"000);"+writer.NewLine;
381 scriptString += @" }"+writer.NewLine;
382 scriptString += @"<";
383 scriptString += @"/";
384 scriptString += @"script>"+writer.NewLine;
385
386 writer.Write(writer.NewLine);
387 writer.Write(scriptString);
388 writer.Write(writer.NewLine);
389 base.Render(writer);
390 }
391 }
392
393
394 注意以上四个类同属于一个名为OnlineUser的工程,他们的命名空间为OnlineUser,编译生成一个dll。
395
396
397
398
399 ===============================================
400
401
402
403 下面我简单介绍一下调用方法:
404
405 1、 新建一个名为OnlineUserDemo的asp.net web应用程序
406 2、 在vs的工具箱选项卡上右击,选择[添加/移除项],浏览定位到OnlineUser.dll,确定即可把Refresh 和Script添加到工具箱。
407 3、 把自动生成的WebForm1.aspx删除,并设置web.config
408 <appSettings>
409 <add key="ActiveTimeout" value="30" />
410 <add key="RefreshTimeout" value="1" />
411 <add key="refreshUrl" value="refresh.aspx" />
412 </appSettings>
413 4、添加一个名为Online.aspx的web窗体,给该窗体添加一个Script控件,一个DataGrid控件(id为DataGrid1),两个 HyperLink控件(分别链接到login.aspx和logout.aspx,text属性分别设置为“登陆”和“注销”),调整好四个控件的位置,转到codebehind,在Page_Load中加入如下代码:
414 string myTicket=(string)this.Page.Session["Ticket"];
415 if(myTicket!=null)
416 {
417 OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
418 if(myPassPort.IsOnline_byTicket(this.Session["Ticket"].ToString()))
419 {
420 myPassPort.ActiveTime(this.Session["Ticket"].ToString());
421 DataGrid1.DataSource=myPassPort.ActiveUsers;
422 DataGrid1.DataBind();
423 }
424 else{
425 //若在线用户列表中找不到当前用户,则定向到注销页面
426 Response.Redirect("Logout.aspx");
427 }
428 }
429 else{
430 Response.Redirect("Login.aspx");
431 }
432 5、添加一个名为login.aspx的web窗体,给该窗体添加一个label控件(id为Label1),设置text属性为“输入一个用户名”,再添加一个textbox控件(id为TextBox1)和一个button控件(id为Button1),调整好他们的位置,双击Button1控件转到 codebehind,为Button1的Click事件加入如下代码:
433 if(TextBox1.Text.Trim()=="")
434 {
435 //不能为空
436 String scriptString = @"<script language=JavaScript>";
437 scriptString += @"alert(""输入一个用户名\n"");";
438 scriptString += @"history.go(-1);";
439 scriptString += @"<";
440 scriptString += @"/";
441 scriptString += @"script>";
442 if(!this.Page.IsStartupScriptRegistered("Startup"))
443 this.Page.RegisterStartupScript("Startup", scriptString);
444 }
445 else{
446 OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
447 string myTicket=DateTime.Now.ToString("yyyyMMddHHmmss");
448 string myUser=TextBox1.Text.Trim();
449 string myClintIP=this.Request.UserHostAddress;
450 this.Session["Ticket"]=myTicket;
451 OnlineUser.ActiveUser myActiveUser=new OnlineUser.ActiveUser(myTicket,myUser,myUser,"test",myClintIP);
452 myPassPort.Login(myActiveUser,true);
453 Response.Redirect("Online.aspx");
454 }
455 6、 添加一个名为logout.aspx的web窗体,给该窗体添加一个HyperLink控件,指向login.aspx,text属性设置为“重登陆”转到codebehind,在Page_Load中加入如下代码:
456 OnlineUser.PassPort myPassPort= new OnlineUser.PassPort();
457 myPassPort.Logout(this.Session["Ticket"].ToString());
458 this.Session["Ticket"]="";
459
460 7、 添加一个名为Refresh.txt的文本文件,设置其内容为:
461 <%@ Register TagPrefix="cc2" Namespace="OnlineUser" Assembly="OnlineUser" %>
462 <%@ Page %>
463 <cc2:Refresh id="myRefresh" runat="server"></cc2:Refresh>
464 把Refresh.txt改名为Refresh.aspx
465
466 8、 编译生成工程。
467
468 ===============================================
469
470
471 下面进行功能测试:
472
473 1、 打开浏览器,在地址栏输入
474 http://你机器的IP地址/onlineuserdemo/Login.aspx
475 2、 输入一个用户名(假设是test1)登陆,自动转到online.aspx页面
476 3、 找同网段的另外一台机器(设你的机器为a,这台机器为b),重复执行第一步。
477 4、 输入一个用户名(假设是test2)登陆,自动转到online.aspx页面
478 5、 在b机器不断刷新online.aspx,若发现test1用户RefreshTime每过25秒自动更新一次而ActiveTime不变(这个时候a机器不要刷新页面啊),则证明a机器的自动刷新生效。
479 6、 在a机器不断刷新online.aspx,若发现test2用户RefreshTime每过25秒自动更新一次而ActiveTime不变(这个时候b机器不要刷新页面啊),则证明b机器的自动刷新生效。
480 7、 直接关闭一台机器(假设是a)上的online.aspx浏览窗口,在另一台机器(就是b啦)上刷新online.aspx,若发现1分钟后test1掉线在线用户只剩下test2,证明通过_refreshTimeout清除在线用户成功。
481 8、 若5、6、7三步正常,则大功告成,否则就再调试调试~~
482
483
484