hashtable 中shiro 超时 自动跳转sessionid会自动清除吗

一.设置web.config相关选项
先启用窗体身份验证和默认登陆页,如下。
&authentication mode=&Forms&&
&forms loginUrl=&default.aspx&&&/forms&
&/authentication&
设置网站可以匿名访问,如下
&authorization&
&allow users=&*&/&
&/authorization&
然后设置跟目录下的admin目录拒绝匿名登陆,如下。注意这个小节在System.Web小节下面。
&location path=&admin&&
&system.web&
&authorization&
&deny users=&?&&&/deny&
&/authorization&
&/system.web&
&/location&
把http请求和发送的编码设置成GB2312,否则在取查询字符串的时候会有问题,如下。
&globalization requestEncoding=&gb2312& responseEncoding=&gb2312&/&
设置session超时时间为1分钟,并启用cookieless,如下。
&sessionState mode=&InProc& cookieless=&true& timeout=&1&/&
为了启用页面跟踪,我们先启用每一页的trace,以便我们方便的调试,如下。
&trace enabled=&true& requestLimit=&1000& pageOutput=&true& traceMode=&SortByTime& localOnly=&true&/&
二.设置Global.asax文件
处理Application_Start方法,实例化一个哈希表,然后保存在Cache里
protectedvoid Application_Start(Object sender, EventArgs e)
Hashtable h=new Hashtable();
Context.Cache.Insert(&online&,h);
在Session_End方法里调用LogoutCache()方法,方法源码如下
///&summary&
/// 清除Cache里当前的用户,主要在Global.asax的Session_End方法和用户注销的方法里调用
///&/summary&
publicvoid LogoutCache()
Hashtable h=(Hashtable)Context.Cache[&online&];
if(h!=null)
if(h[Session.SessionID]!=null)
h.Remove(Session.SessionID);
Context.Cache[&online&]=h;
三.设置相关的登陆和注销代码
&&&&& 登陆前调用PreventRepeatLogin()方法,这个方法可以防止用户重复登陆,如果上次用户登陆超时大于1分钟,也就是关闭了所有admin目录下的页面达到60秒以上,就认为上次登陆的用户超时,你就可以登陆了,如果不超过60秒,就会生成一个自定义异常。在Cache[&online&]里保存了一个哈西表,哈西表的key是当前登陆用户的SessionID,而Value是一个ArrayList,这个ArrayList有两个元素,第一个是用户登陆的名字第二个元素是用户登陆的时间,然后在每个admin目录下的页刷新页面的时候会更新当前登陆用户的登陆时间,而只admin目录下有一个页打开着,即使不手工向服务器发送请求,也会自动发送一个请求更新登陆时间,下面我在页面基类里写了一个函数来做到这一点,其实这会增加服务器的负担,但在一定情况下也是一个可行的办法.
///&summary&
/// 防止用户重复登陆,在用户将要身份验证前使用
///&/summary&
///&param name=&name&&要验证的用户名字&/param&
privatevoid PreventRepeatLogin(string name)
Hashtable h = (Hashtable)Cache[&online&];
if (h !=null)
IDictionaryEnumerator e1 = h.GetEnumerator();
bool flag =
while (e1.MoveNext())
if ((string)((ArrayList)e1.Value)[0] == name)
TimeSpan ts = System.DateTime.Now.Subtract(Convert.ToDateTime(((ArrayList)e1.Value)[1]));
if (ts.TotalSeconds &60)
thrownew oa.cls.MyException(&对不起,你输入的账户正在被使用中,如果你是这个账户的真正主人,请在下次登陆时及时的更改你的密码,因为你的密码极有可能被盗窃了!&);
h.Remove(e1.Key);
h =new Hashtable();
ArrayList al =new ArrayList();
al.Add(name);
al.Add(System.DateTime.Now);
h[Session.SessionID] =
if (Cache[&online&] ==null)
Context.Cache.Insert(&online&, h);
Cache[&Online&] =
用户注销的时候调用上面提到LogoutCache()方法
四.设置admin目录下的的所有页面的基类
using System.W
using System.Web.UI;
using System.Web.UI.WebC
using System.Web.UI.HtmlC
using System.C
namespace oa.cls
publicclass MyBasePage : System.Web.UI.Page
///&summary&
/// 获取本页是否在受保护目录,我这里整个程序在OA的虚拟目录下,受保护的目录是admin目录
///&/summary&
protectedbool IsAdminDir
return Request.FilePath.IndexOf(&/oa/admin&) ==0;
///&summary&
/// 防止session超时,如果超时就注销身份验证并提示和转向到网站默认页
///&/summary&
privatevoid PreventSessionTimeout()
if(!this.IsAdminDir)
if(Session[&User_Name&]==null&&this.IsAdminDir)
System.Web.Security.FormsAuthentication.SignOut();
this.Alert(&登陆超时&,Request.ApplicationPath)
///&summary&
/// 每次刷新本页面的时候更新Cache里的登陆时间选项,在下面的OnInit方法里调用.
///&/summary&
privatevoid UpdateCacheTime()
Hashtable h = (Hashtable)Cache[&online&];
if (h !=null)
((ArrayList)h[Session.SessionID])[1] = DateTime.N
Cache[&Online&] =
///&summary&
/// 在跟踪里输出一个HashTable的所有元素,在下面的OnInit方法里调用.以便方便的观察缓存数据
///&/summary&
///&param name=&myList&&&/param&
privatevoid TraceValues(Hashtable myList)
IDictionaryEnumerator myEnumerator = myList.GetEnumerator();
while (myEnumerator.MoveNext())
Context.Trace.Write(&onlineSessionID&+ i, myEnumerator.Key.ToString());
ArrayList al = (ArrayList)myEnumerator.V
Context.Trace.Write(&onlineName&+ i, al[0].ToString());
Context.Trace.Write(&onlineTime&+ i, al[1].ToString());
TimeSpan ts = System.DateTime.Now.Subtract(Convert.ToDateTime(al[1].ToString()));
Context.Trace.Write(&当前的时间和此登陆时间间隔的秒数&, ts.TotalSeconds.ToString());
i++;
///&summary&
/// 弹出信息并返回到指定页
///&/summary&
///&param name=&msg&&弹出的消息&/param&
///&param name=&url&&指定转向的页面&/param&
protectedvoid Alert(string msg, string url)
string scriptString =&&script language=JavaScript&alert(\&& + msg + &\&);location.href=\&& + url + &\&&/script&&;
if (!this.IsStartupScriptRegistered(&alert&))
this.RegisterStartupScript(&alert&, scriptString);
///&summary&
/// 为了防止常时间不刷新页面造成会话超时,这里写一段脚本,每隔一分钟向本页发送一个请求以维持会话不被超时,这里用的是xmlhttp的无刷新请求
/// 这个方法也在下面的OnInit方法里调用
///&/summary&
protectedvoid XmlReLoad()
System.Text.StringBuilder htmlstr =new System.Text.StringBuilder();
htmlstr.Append(&&SCRIPT LANGUAGE=\&JavaScript\&&&);
htmlstr.Append(&function GetMessage(){&);
htmlstr.Append(& var xh=new ActiveXObject(\&Microsoft.XMLHTTP\&);&);
htmlstr.Append(& xh.open(\&get\&,window.location,false);&);
htmlstr.Append(& xh.send();&);
htmlstr.Append(& window.setTimeout(\&GetMessage()\&,60000);&);
htmlstr.Append(&}&);
htmlstr.Append(&window.onload=GetMessage();&);
htmlstr.Append(&&/SCRIPT& &);
if (!this.IsStartupScriptRegistered(&xmlreload&))
this.RegisterStartupScript(&alert&, htmlstr.ToString());
overrideprotectedvoid OnInit(EventArgs e)
base.OnInit(e);
this.PreventSessionTimeout();
this.UpdateCacheTime();
this.XmlReLoad();
if (this.Cache[&online&] !=null)
this.TraceValues((System.Collections.Hashtable)Cache[&online&]);
五.写一个自定义异常类
首先要在跟目录下写一个错误显示页面ShowErr.aspx,这个页面根据传递过来的查询字符串msg的值,在一个Label上显示错误信息。
namespace oa.cls
///&summary&
/// MyException 的摘要说明。
///&/summary&
publicclass MyException : ApplicationException
///&summary&
/// 构造函数
///&/summary&
public MyException()
///&summary&
/// 构造函数
///&/summary&
///&param name=&ErrMessage&&异常消息&/param&
public MyException(string Message)
: base(Message)
System.Web.HttpContext.Current.Response.Redirect(&~/ShowErr.aspx?msg=&+ Message);
///&summary&
/// 构造函数
///&/summary&
///&param name=&Message&&异常消息&/param&
///&param name=&InnerException&&引起该异常的异常类&/param&
public MyException(string Message, Exception InnerException)
: base(Message, InnerException)
&&相关文章推荐
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:69593次
积分:1081
积分:1081
排名:千里之外
转载:187篇
评论:19条
(1)(8)(1)(14)(2)(1)(4)(2)(2)(1)(21)(20)(21)(3)(1)(1)(4)(1)(11)(3)(15)(8)(12)(29)(5)(2)(2)下次自动登录
现在的位置:
& 综合 & 正文
C# 实现的多线程异步Socket数据包接收器框架
几天前在博问中看到一个,就想到笔者2004年做的一个省级交通流量接收服务器项目,当时的基本求如下:
接收自动观测设备通过无线网卡、Internet和Socket上报的交通量数据包
全年365*24运行的自动观测设备5分钟上报一次观测数据,每笔记录约2K大小
规划全省将有100个左右的自动观测设备(截止2008年10月还只有30个)
当时,VS2003才发布年多,笔者也是接触C#不久。于是Google了国内国外网,希望找点应用C#解决Socket通信问题的思路和。最后,找
到了两篇帮助最大的:一篇是国人写的Socket接收器框架,应用了独立的客户端Socket会话(Session)概念,给笔者提供了一个接收服务
器的总体框架思路;另一篇是美国人写的,提出了多线程、分段接收数据包的技术方案,描述了多线程、异步Socket的许多实现细节,该文坚定了笔者采用多
线程和异步方式处理Socket接收器的技术路线。
具体实现和测试时笔者还发现,在Internet环境下的Socket应用中,需要系统有极强的容错能力:没有办法控制异常,就必须允许它们存在(附加源
代码中可以看到,try{}catch{}语句较多)。对此,笔者设计了一个专门的检查和清理线程,完成无效或超时会话的清除和资源释放工作。
依稀记得,国内框架作者的名称空间有ibm,认为是IBM公司职员,通过邮件后才知道其人在深圳。笔者向他请教了几个问题,相互探讨了几个技术关键点。可
惜,现在再去找,已经查不到原文和邮件了。只好借此机会,将本文献给这两个素未谋面的技术高人和同行,也盼望拙文或源码能给读者一点有用的启发和帮助。
1、主要技术思路
整个系统由三个核心线程组成,并由.NET线程池统一管理:
侦听客户端连接请求线程:ListenClientRequest(),循环侦听客户端
连接请求。如果有,检测该客户端IP,看是否是同一观测设备,然后建立一个客户端TSession对象,并通过Socket异步调用方法
BeginReceive()接收数据包、EndReceive()处理数据包
数据包处理线程:HandleDatagrams(),循环检测数据包队列_datagramQueue,完成数据包解析、判断类型、存储等工作
客户端状态检测线程:CheckClientState(),循环检查客户端会话表_sessionTable,判断会话对象是否有效,设置超时会话关闭标志,清楚无效会话对象及释放其资源
2、主要类简介
系统主要由3个类组成:
TDatagramReceiver(数据包接收服务器):系统的核心进程类,建立Socket连接、处理与存储数据包、清理系统资源,该类提供全部的public属性和方法
TSession(客户端会话):由每个客户端的Socket对象组成,有自己的数据缓冲区,清理线程根据该对象的最近会话时间判断是否超时
TDatagram(数据包类):判断数据包类别、解析数据包
3、关键函数和代码
下面简介核心类TDatagramReceiver的关键实现代码。
系统启动方法StartReceiver()首先清理资源、创建数据库连接、初始化若干计数值,然后创建服务器端侦听Socket对象,最后调用静态方法ThreadPool.QueueUserWorkItem()在线程池中创建3个核心处理线程。
Code/// &summary&///
启动接收器/// &/summary&public bool StartReceiver()
_stopReceiver = true;
this.Close();
if (!this.ConnectDatabase()) return false;
_clientCount = 0;
_datagramQueueCount = 0;
_datagramCount = 0;
_errorDatagramCount = 0;
_exceptionCount = 0;
_sessionTable = new Hashtable(_maxAllowClientCount);
_datagramQueue = new Queue&TDatagram&(_maxAllowDatagramQueueCount);
_stopReceiver = false;
// 循环中均要该标志
if (!this.CreateReceiverSocket())
//建立服务器端 Socket 对象
return false;
// 侦听客户端连接请求线程, 使用委托推断, 不建 CallBack 对象
if (!ThreadPool.QueueUserWorkItem(ListenClientRequest))
return false;
// 处理数据包队列线程
if (!ThreadPool.QueueUserWorkItem(HandleDatagrams))
return false;
// 检查客户会话状态, 长时间未通信则清除该对象
if (!ThreadPool.QueueUserWorkItem(CheckClientState))
return false;
_stopConnectRequest = false;
// 启动接收器,则自动允许连接
this.OnReceiverException();
_stopReceiver = true;
return !_stopR
下面是创建侦听Socket对象的方法代码。
Code/// &summary&/// 创建接收服务器的 Socket, 并侦听客户端连接请求/// &/summary&private bool CreateReceiverSocket()
_receiverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_receiverSocket.Bind(new IPEndPoint(IPAddress.Any, _tcpSocketPort));
// 绑定端口
_receiverSocket.Listen(_maxAllowListenQueueLength);
// 开始监听
return true;
this.OnReceiverException();
return false;
侦听客户端连接请求
服务器端循环等待客户端连接请求。一旦有请求,先判断客户端连接数是否超限,接着检测该客户端IP地址,一切正常后建立TSession对象,并调用异步方法接收客户端Socket数据包。
代码中,Socket读到数据时的回调AsyncCallback委托方法EndReceiveData()完成数据接收工作,正常情况下启动另一个异步BeginReceive()调用。
.NET中,每个异步方法都有自己的独立线程,异步处理其实也基于多线程机制的。下面代码中的异步套异步调用,既占用较大的系统资源,也给处理带来意想不到的结果,更是出现异常时难以控制和处理的关键所在。
Code/// &summary&/// 循环侦听客户端请求,由于要用线程池,故带一个参数/// &/summary&private void ListenClientRequest(object state)
Socket client = null;
while (!_stopReceiver)
if (_stopConnectRequest)
停止客户端连接请求
if (_receiverSocket != null)
_receiverSocket.Close();
// 强制关闭接收器
this.OnReceiverException();
// 必须为 null,否则 disposed 对象仍然存在,将引发下面的错误
_receiverSocket = null;
if (_receiverSocket == null)
if (!this.CreateReceiverSocket())
if (_receiverSocket.Poll(_loopWaitTime, SelectMode.SelectRead))
// 频繁关闭、启动时,这里容易产生错误(提示套接字只能有一个)
client = _receiverSocket.Accept();
if (client != null && client.Connected)
if (this._clientCount &= this._maxAllowClientCount)
this.OnReceiverException();
client.Shutdown(SocketShutdown.Both);
client.Close();
else if (CheckSameClientIP(client))
// 已存在该 IP 地址
client.Shutdown(SocketShutdown.Both);
client.Close();
TSession session = new TSession(client);
session.LoginTime = DateTime.N
lock (_sessionTable)
int preSessionID = session.ID;
while (true)
if (_sessionTable.ContainsKey(session.ID))
// 有可能重复该编号
session.ID = 100000 + preSessionID;
_sessionTable.Add(session.ID, session);
// 登记该会话客户端
Interlocked.Increment(ref _clientCount);
this.OnClientRequest();
// 客户端连续连接或连接后立即断开,易在该处产生错误,系统忽略之
// 开始接受来自该客户端的数据
session.ClientSocket.BeginReceive(session.ReceiveBuffer, 0,
session.ReceiveBufferLength, SocketFlags.None, EndReceiveData, session);
session.DisconnectType = TDisconnectType.E
session.State = TSessionState.NoR
else if (client != null)
// 非空,但没有连接(connected is false)
client.Shutdown(SocketShutdown.Both);
client.Close();
this.OnReceiverException();
if (client != null)
client.Shutdown(SocketShutdown.Both);
client.Close();
// 该处可以适当暂停若干毫秒
// 该处可以适当暂停若干毫秒}
/// &summary&/// 循环侦听客户端请求,由于要用线程池,故带一个参数/// &/summary&private void ListenClientRequest(object state)
Socket client = null;
while (!_stopReceiver)
if (_stopConnectRequest)
停止客户端连接请求
if (_receiverSocket != null)
_receiverSocket.Close();
// 强制关闭接收器
this.OnReceiverException();
// 必须为 null,否则 disposed 对象仍然存在,将引发下面的错误
_receiverSocket = null;
if (_receiverSocket == null)
if (!CreateReceiverSocket())
if (_receiverSocket.Poll(_loopWaitTime, SelectMode.SelectRead))
// 频繁关闭、启动时,这里容易产生错误(提示套接字只能有一个)
client = _receiverSocket.Accept();
if (client != null && client.Connected)
if (this._clientCount &= this._maxAllowClientCount)
this.OnReceiverException();
client.Shutdown(SocketShutdown.Both);
client.Close();
else if (CheckSameClientIP(client))
// 已存在该 IP 地址
client.Shutdown(SocketShutdown.Both);
client.Close();
TSession session = new TSession(client);
session.LoginTime = DateTime.N
lock (_sessionTable)
int preSessionID = session.ID;
while (true)
if (_sessionTable.ContainsKey(session.ID))
// 有可能重复该编号
session.ID = 100000 + preSessionID;
_sessionTable.Add(session.ID, session);
// 登记该会话客户端
Interlocked.Increment(ref _clientCount);
OnClientRequest();
// 客户端连续连接或连接后立即断开,易在该处产生错误,系统忽略之
// 开始接受来自该客户端的数据
session.ClientSocket.BeginReceive(session.ReceiveBuffer, 0,
session.ReceiveBufferLength, SocketFlags.None, EndReceiveData, session);
session.DisconnectType = TDisconnectType.E
session.State = TSessionState.NoR
else if (client != null)
// 非空,但没有连接(connected is false)
client.Shutdown(SocketShutdown.Both);
client.Close();
this.OnReceiverException();
if (client != null)
client.Shutdown(SocketShutdown.Both);
client.Close();
// 该处可以适当暂停若干毫秒
// 该处可以适当暂停若干毫秒}
处理数据包
该线程循环查看数据包队列,完成数据包的解析与存储等工作。具体实现时,如果队列中没有数据包,可以考虑等待若干毫秒,提高CPU利用率。
Codeprivate void HandleDatagrams(object state)
while (!_stopReceiver)
this.HandleOneDatagram();
// 处理一个数据包
if (!_stopReceiver)
// 如果连接关闭,则重新建立,可容许几个连接错误出现
if (_sqlConnection.State == ConnectionState.Closed)
this.OnReceiverWork();
_sqlConnection.Open();
this.OnReceiverException();
/// &summary&/// 处理一个包数据,包括:验证、存储/// &/summary&private void HandleOneDatagram()
TDatagram datagram = null;
lock (_datagramQueue)
if (_datagramQueue.Count & 0)
datagram = _datagramQueue.Dequeue();
// 取队列数据
Interlocked.Decrement(ref _datagramQueueCount);
if (datagram == null) return;
datagram.Clear();
datagram = null;
// 释放对象}
检查与清理会话
本线程负责处理建立连接后的客户端会话TSession或Socket对象的关闭与资源清理工作,其它方法中出现异常等情况,尽可能标记相关TSession对象的属性NoReply=true,表示该会话已经无效、需要清理。
检查会话队列并清理资源分3步:第一步,Shutdown()客户端Socket,此时可能立即触发某些Socket的异步方法
EndReceive();第二步,Close()客户端Socket,释放占用资源;第三步,从会话表中清除该会话对象。其中,第一步完成后,某个
TSession也许不会立即到第二步,因为可能需要处理其异步结束方法。
需要指出, 由于涉及多线程处理,需要频繁加解锁操作,清理工作前先建立一个会话队列列副本sessionTable2,检查与清理该队副本列列的TSession对象。
Code/// &summary&/// 检查客户端状态(扫描方式,若长时间无数据,则断开)/// &/summary&private void CheckClientState(object state)
while (!_stopReceiver)
DateTime thisTime = DateTime.N
// 建立一个副本 ,然后对副本进行操作
Hashtable sessionTable2 = new Hashtable();
lock (_sessionTable)
foreach (TSession session in _sessionTable.Values)
if (session != null)
sessionTable2.Add(session.ID, session);
foreach (TSession session in sessionTable2.Values)
// 对副本进行操作
Monitor.Enter(session);
if (session.State == TSessionState.NoReply)
// 分三步清除一个 Session
session.State = TSessionState.C
if (session.ClientSocket != null)
// 第一步:shutdown
session.ClientSocket.Shutdown(SocketShutdown.Both);
else if (session.State == TSessionState.Closing)
session.State = TSessionState.C
if (session.ClientSocket != null)
// 第二步: Close
session.ClientSocket.Close();
else if (session.State == TSessionState.Closed)
lock (_sessionTable)
// 第三步:remove from table
_sessionTable.Re
&&&&推荐文章:
【上篇】【下篇】}

我要回帖

更多关于 sessionid清除session 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信