Asp.Net在線統計用戶列表

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
{
///
/// Global 的摘要說明。
///
public class Global : System.Web.HttpApplication
{
private static System.Threading.Timer timer;
private const int interval = 1000 * 60 * 10;//檢查在線用戶的間隔時間

///
/// 必需的設計器變量。
///
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)
{

}

#region Web 窗體設計器生成的代碼
///
/// 設計器支持所需的方法 - 不要使用代碼編輯器修改
/// 此方法的內容。
///
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 {
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中註冊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章