NULL

坚持点,平和点...【My open asp.net control projects http://www.codeplex.com/aspnetControl/】

  博客园 :: 首页 :: 新随笔 ::  :: 订阅 订阅 :: 管理 ::
  31 随笔 :: 1 文章 :: 897 评论 :: 39 Trackbacks
 

最近,公司有个BS结构的项目的deadline临近,但是在处理“单人登入,即时登出”时,一直没有找到一个比较理想的方案,因为使用这个项目的客户群是警察叔叔,如果在登入登出的时候“卡壳”,那我不要混了,公司也得玩完。

防止同一帐号多人同时登入的一般解决方案,无非就是把登入时的用户ID保存在数据库中,或application中,或cache中,然后在用户登出的时候删除用户ID,但是要删除用户ID还是有点棘手。

不管用户ID保存在哪里,当我点击“登出”button的时候,都需要我们通过一个事件去告诉系统,用户ID现在该清除了。假设我把用户ID保存到cache中,并设置cache的销毁时间为Session的结束时间,Session一结束,cache跟着销毁,这样设计也合情合理。

如果您一相情愿的认为使用者都会去点击“登出”button,那就大错特错了,我平常也喜欢直接XIE,懒得去点那小小的“登出”button。可能有人马上会说,可以用js来判断浏览器是否关闭了呀!没错,js很容易判断IE是否关闭。但是,如果电源插头不小心被你那急着上WC的,有点肾亏同事绊掉了呢?那些警察叔叔就只能干等着session自动过期,如果Timeout设置得稍微长一点,客户那边马上电话不断,然后,公司技术员不得不restart服务器。公司以前有些系统就如此,老要restart服务器,我是看在眼上,痛在心里。如果还有人要说,俺有UPS,怕什么,那我也不好再说什么了^-^

我想,如果单靠JS来判断,“年长日久,难免发生意外”,怎么办呢?能不能让服务器自己来判断呢?这还问,当然可以,把sessionTimeout的值设置小点,不就得了!如果只个值太小,等警察叔叔抽完一根烟,再使用系统的时候,系统马上提示;“您的会话已经过期,请重新登录“。如果老是要登入,那就玩不下去了。能不能想个办法,不让系统会话很快过期,又能在一个合适的时间让系统自动注销呢?可能没等我说完,马上有人会说,在client端适时的激活服务器,不就得了。您说的没错,我也是这么想的。

那我就按您的思路去实现吧;

首先设置web.config文件;

<sessionState mode="StateServer" cookieless="false" stateConnectionString="tcpip=127.0.0.1:42424" sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes" timeout="1"/>

       然后在登入事件中加入;

       ///同一时间点,检查登录用户ID的唯一性

///</summary>

    ///<param name="strZh">用户ID </param>

    ///<returns></returns>

    private bool ckeckUserOnlyOne(string strZh)

    {

        string strTempZh=strZh;

        if (Cache[strZh] == null)

        {

            TimeSpan SessTimeout = new TimeSpan(0,0,System.Web.HttpContext.Current.Session.Timeout,0,0);

            HttpContext.Current.Cache.Insert(strTempZh,strTempZh,null, DateTime.MaxValue, SessTimeout, System.Web.Caching.CacheItemPriority.NotRemovable, null);

            return true;

        }

        else

            return false;

}

       然后在登出事件加入;

          try

        {

            Session.Abandon();

            System.Web.HttpContext.Current.Session.RemoveAll();

            Cache.Remove("用户ID");

        }

        catch (Exception ex)

        { }

主角登场了,在每个页面引入下面的JS;

<script>

var step=0;

    function myRefresh()

    {

        var httpRequest = new ActiveXObject("microsoft.xmlhttp");

        httpRequest.open("GET", "delSession.aspx", false);

        httpRequest.send(null);

        step ++;

        if(step <2)//注意2

        {

            setTimeout("myRefresh()",30*1000); //30

        }

    }

    myRefresh();

</script>

     最后,别忘记项目的Root目录下new一个delSession.aspx文件,然后在她的page_laod方法中写上Response.Expires = -1;

     按ctr+F5,期间不要激活,是不是在2分种之后,您的系统提示会话过期了呢?哈哈

由于代码比较简单,就不一一解释了,讲一下思路;

首先在未继续激活会话的情况下,设置她的生命周期为一分钟,我感觉一分钟刚好,(哎,自古红颜多薄命!)

然后,在判断此时userID 没有登入时,把user ID 保存在cache中,且cache的过期时间为SessTimeout,也就是说当前会话一销毁,cache也就跟着殉情。(烈女啊!)

     又然后,在“登出“事件中注销当前会话,清除cache集合中的当前user ID,这样就可以在点击“登出”button时,及时清除对应的cache项。

     再然后,在页面的JS中通过setTimeout("myRefresh()",30*1000)方法,不断请求激活服务器,直至玩不下去,注意并没有获取响应。

     再然后,在被请求的页面中设置自己不使用“页面输出缓存”,以免无法激活当前会话。

现在已经是0:16分了,长沙的冬天晚上比较寒冷,刚刚泡的大麦红茶已经凉了,再罗嗦一句,可以修改“step <2”和setTimeout("myRefresh()",30*1000),把这些参数放到配置文件中,可以随便修改,直到找到最佳平衡点,祝大家好运!

后记;

     言多必失,说得不妥当之处,还望多多指教!

posted on 2008-02-19 09:53 王孟军! 阅读(3082) 评论(46)  编辑 收藏 所属分类: asp.net

评论

#1楼  2008-02-19 10:09 t-mac.NET      
呵呵,这也行啊
  回复  引用  查看    

#2楼  2008-02-19 10:09 silent x [未注册用户]
放在框架页应该更好

这样就不用每个页面都加入了
  回复  引用    

这个方法是行得通,但是好像对性能方面影响比较大
  回复  引用    

#4楼  2008-02-19 10:15 Ariel Y.      
何不试试window.onunload
  回复  引用  查看    

#5楼  2008-02-19 10:24 马可香蕉      
不错的方法
  回复  引用  查看    

用一个隐藏的IFrame来定时刷吧。多年前在CSDN上讨论了半天最后就只剩下这种办法了。
  回复  引用    

#7楼  2008-02-19 10:58 kmfree      
以前也碰到过类似的问题,一直没找到好方法,谢谢了:)
  回复  引用  查看    

#8楼  2008-02-19 11:42 大力      
用JS来控制这么重要的工作总是很不放心。。。。/没有安全感
  回复  引用  查看    

#9楼  2008-02-19 12:26 胖子      
隐藏IFrame刷新空白页才素王道。
另:没有不安全的js,只有不安全的程序
  回复  引用  查看    

#10楼  2008-02-19 12:27 随心所欲      
1: 牺牲了不少效率。
如果同时有100人在线,每小时垃圾访问就是5-6k个。
2:每页设置js,这可以说OO设计有问题。
完全可以设置一个基类,或者masterPage,很多方法可以避免这种设计。
3:对B的依赖性加强了,需要考虑客户的IE都是正常的。
事实上并非如此。如果IE有问题,你的js将不能正常运作。造成的结果是:一直需要登录。
  回复  引用  查看    

#11楼  2008-02-19 12:34 w荒v原v狼w      
顶6楼鄙人也支持用IFrame定时刷新好用^_^
  回复  引用  查看    

#12楼  2008-02-19 12:40 kangnoz [未注册用户]
我用了你的方法,好像无法激活session啊?
  回复  引用    

无话可说,顶吧
  回复  引用    

#14楼  2008-02-19 13:01 xins [未注册用户]
step => 2 不是就不刷了.
  回复  引用    

#15楼 [楼主] 2008-02-19 13:01 wmj      
致@kangnoz,随心所欲 ,大力 ,风中的落叶,和个位!
首先谢谢个位的指教

然后,在此更正一些不妥之处;

一,最好不要在每个页面上都引用刷新的JS,可以考虑用IFRAME或者Masterpage,减少刷新次数。

二,用来刷新的JS都是通用的demo,各种浏览器都有效。

三,就算浏览器出了问题,JS不能正常RUN,但是否记得Session的timeout="1",一分钟后,就可以重新登入。我想,一分钟不是很过分。

四,对于性能问题,我不便说得太多,因为得看数据,对于中小型的SITE,我想应该没有问题。
  回复  引用  查看    

#16楼  2008-02-19 14:10 随心所欲      
一分钟不是很过分
-------------------------
jc叔叔录入数据需要多长时间?他们精神不崩溃算你幸运了。
  回复  引用  查看    

#17楼 [楼主] 2008-02-19 14:54 wmj      
@随心所欲
如果你有更好的方案,不妨让大家学习学习。
本来写BLOGS就是大家相互学习,不管对错,应该相互尊敬。

  回复  引用  查看    

#18楼  2008-02-19 15:19 极地雪狼      
这个方法不错,对服务器负载也不是太大。只要你建立一个空文件就够了。在JS返回的时候是个空值就成。

  回复  引用  查看    

#19楼  2008-02-19 15:21 PerfectDesign      
应该可以使用Session过期来判断就可以了吧?
Session判断如果有重复的用户名在Session中,那么就是重复登陆
Session过期就证明需要用户重新登陆
client定时请求服务器这个万万不行。如果一个用户打开N个页面,不但服务器挂,而且客户端的CPU也面临很大压力。
毕竟xmlhttp还是比较占客户端内存的。
  回复  引用  查看    

#20楼  2008-02-19 16:11 随心所欲      
--引用--------------------------------------------------
wmj: @随心所欲
如果你有更好的方案,不妨让大家学习学习。
本来写BLOGS就是大家相互学习,不管对错,应该相互尊敬。

--------------------------------------------------------
我指出这个方案的可能存在的缺点。
我总共回复两次,哪里不尊重你了?没有吧。技术讨论,别这么敏感。

补充一个:我也给jc叔叔们做过程序,多少了解他们对电脑的掌握程度。1分钟他们很可能没有完成数据录入,如果这时候提示登出,他们不精神崩溃的话,绝对算你幸运。
所以,就算这个单人登录不实现,也不要造成这种可怕的后果。
  回复  引用  查看    

#21楼 [楼主] 2008-02-19 16:22 wmj      
致@随心所欲,和各位兄弟
谢谢“随心所欲的”的指导,
我想到一个优化性能的办法,可以让delSession.aspx页面文件不回发到客户端,思路是在样的;
重写 IHttpHandler接口,然后在IHttpHandler.ProcessRequest中人为中断Http的响应流,这样应该可以进一步的提升性能,以后抽时间补上。

还想再补上一句
引用“随心所欲”的一句话
“1分钟他们很可能没有完成数据录入,如果这时候提示登出”

你可能没看明白上下文的意思,我这里Session的timeout虽然只有1分钟,但是,client端会激活,所以不会象你想的那样 一分钟就 要求再次登入。
下面是demo,你可以修改
if(step <2)//注意2
{
setTimeout("myRefresh()",30*1000); //30秒
}
比如,可以把step<2改成step<60次,这样就算JC叔叔什么也不干,Session也可以维持30分钟。注意,这还是“警察叔叔什么都不干”的情况下的时间。



  回复  引用  查看    

#22楼  2008-02-19 16:32 随心所欲      
@wmj
1:是讨论,而非指导
2:IHttpHandler和apsx差不了太多,效率只是略有提升。
3:如果只是提高访问性能的话,似乎可以用htm来代替IHttpHandler,这样效率更高。Session依然被激活的。效果和访问aspx,ashx是一样的。

  回复  引用  查看    

#23楼  2008-02-19 16:35 随心所欲      
--引用--------------------------------------------------

引用“随心所欲”的一句话
“1分钟他们很可能没有完成数据录入,如果这时候提示登出”

你可能没看明白上下文的意思,我这里Session的timeout虽然只有1分钟,但是,client端会激活,所以不会象你想的那样 一分钟就 要求再次登入。
下面是demo,你可以修改
if(step <2)//注意2
{
setTimeout("myRefresh()",30*1000); //30秒
}
比如,可以把step<2改成step<60次,这样就算JC叔叔什么也不干,Session也可以维持30分钟。注意,这还是“警察叔叔什么都不干”的情况下的时间。



--------------------------------------------------------
问题在于:如果IE出了问题,那么这个setTimeout将不会执行。也就是1分钟之后Session就会过期。

  回复  引用  查看    

#24楼 [楼主] 2008-02-19 16:43 wmj      
@随心所欲
你说得对,
这也正是 我要的效果。

  回复  引用  查看    

随便,SESSIONID都不会用,光盘。
  回复  引用    

#26楼  2008-02-19 18:38 stone123 [未注册用户]
在服务器端启timer吧
  回复  引用    

#27楼  2008-02-19 18:39 Coderwind [未注册用户]
<sessionState mode="StateServer" cookieless="false" stateConnectionString="tcpip=127.0.0.1:42424" sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes" timeout="1"/>

用inproc模式也没问题吧
  回复  引用    

#28楼  2008-02-19 18:47 Coderwind [未注册用户]
然后在她的page_laod方法中写上Response.Expires = -1;

--------------------------------

这里不用为什么要加上这句话呢,既然没有使用页面缓存输出,这句话存在就没有必要性
  回复  引用    

#29楼  2008-02-19 20:12 Shinn      
前不久想过这个东东

我觉得c/s系统(如腾讯QQ软件)的做法可以参考

session还是设置为20分钟左右(太短有工作做不完)

如果同一ID有新的登陆的话就将原来的登陆强制断开,同时通知就可以了

只允许后来的登陆有效

代码没有实现,也没有实用过,仅供参考
  回复  引用  查看    

#30楼  2008-02-19 21:20 jillzhang      
关键还是不能准确的知道用户什么时候退出
  回复  引用  查看    

#31楼  2008-02-19 21:35 阿瑞--16hi      
@Shinn
这个想法不错,但是怎么实现先登陆的失效,后登陆的有效呢?
  回复  引用  查看    

#32楼  2008-02-19 22:12 Shinn      
--引用--------------------------------------------------
阿瑞--16hi: @Shinn
这个想法不错,但是怎么实现先登陆的失效,后登陆的有效呢?
--------------------------------------------------------
这个的处理方式应该比较简单吧


是不是可以将先登陆者的session删除或进行处理,应该可以吧

或者给每个连接新增一个token,当token有效时才能操作,这样的话就可以将先前登陆者的token在有效token集合中删除就可以了

(注:我自己没有实现过)

  回复  引用  查看    

#33楼 [楼主] 2008-02-20 09:51 wmj      

,@Coderwind ,@Shinn ,@阿瑞--16hi,和各位朋友
谢谢你们提出宝贵的见解。

1:回复 @Coderwind
可以用IIS进程,我的SESSION状态保存在state server服务中

2:回复 @Coderwind
引用,"然后在她的page_laod方法中写上Response.Expires = -1;"
最好加上,因为ASP.NET runtime如果发现你请求的页面,没有改变,那么runtime有可能会从缓存中直接回发给client,。

3:回复 @Shinn and @ 阿瑞--16hi
你们的想法很不错,但是我一直没有找到一个好办法,用来清除 ”前一次登入“时遗留下来的Session,有时间共同探讨探讨。



  回复  引用  查看    

#34楼  2008-02-20 10:24 awen177 [未注册用户]
IFrame定时刷新的方式是怎样实现的?
  回复  引用    

#35楼  2008-02-20 17:29 蜀山雪狼      
@Shinn
我以前的项目就是有这种方式做的,很好,好强大。

以前还看到有的电影网站上,有“掉线自救”功能,如果用户不正确的关掉了浏览器,但是Session还没有过期的情况下,就可以使用掉线自救功能,清除服务器上的Session。


  回复  引用  查看    

#36楼 [楼主] 2008-02-27 09:27 王孟军!      
回复1-35楼
SORRY......
1:昨天测试的时候,发现一个严重的问题,
TimeSpan SessTimeout = new TimeSpan(0,0,System.Web.HttpContext.Current.Session.Timeout,0,0);
上面这句中的,Web.HttpContext.Current.Session.Timeout 的值 就是我们在config中设置的值,所以这里并不需要用Cache了,直接用Session对象保存UserID。
2:这是客户端 最新的JS

var x=0;
function myRefresh()
{

var httpRequest = new ActiveXObject("microsoft.xmlhttp");
httpRequest.open("GET", "ActionServer.aspx", false);
httpRequest.send(null);
x++;
if(x<60) //60次,也就是Session真正的过期时间是30分钟
{
setTimeout("myRefresh()",30*1000); //30秒

}
else
{
httpRequest.open("GET", "delSession.aspx?id=001", false);
httpRequest.send(null);
}

}
myRefresh();

// close ie button
function window.onbeforeunload()
{
if (event.clientX>document.body.clientWidth && event.clientY<0||event.altKey)
{
var httpRequest = new ActiveXObject("microsoft.xmlhttp");
httpRequest.open("GET", "delSession.aspx?id=001", false);
httpRequest.send(null);

}


}


  回复  引用  查看    

#37楼  2008-02-29 09:54 蜀山雪狼      
我所说的“可以使用掉线自救功能,清除服务器上的Session。 ”是说的,在页面上设计一个叫“掉线自救”的功能按钮,用户点击此按钮后,输入用户名和密码提交服务器验证,验证通过后,服务器上将用户Session清除就行了。

不知道我说清楚没有。
  回复  引用  查看    

#38楼 [楼主] 2008-02-29 10:52 王孟军!      
引用
“用户点击此按钮后,输入用户名和密码提交服务器验证,验证通过后”

你的做法 等于是 再次登录,那么第二次登录后的SESSION与前一次登录的SESSION就不同了,不是同一个。

还有 ,你在二次登录的时候,怎样 去“服务器上将用户Session清除就行了”。

我看不行吧,每个用户的SESSION是独立的,而且是安全的。
  回复  引用  查看    

#39楼  2008-03-31 10:25 Coderwind [未注册用户]
楼主的第一个问题,是因为session时间会自动延长,而cache不会,它只是你设定的webcofig的时间,所以不要存在cache里,这样会造成session没过期,cache却过期了。
  回复  引用    

#40楼  2008-04-22 01:53 狼Robot      
不知道楼主想实现什么?

是要实现:像QQ一样,一个号码只能在一个地方登录,如果同时又在另一个地方登录,前一个登录的就要强迫下线吗?
  回复  引用  查看    

#41楼  2008-04-22 01:56 狼Robot      
还是说?用户登录后,如果没有退出,那这个用户就不能再次登录了?
  回复  引用  查看    

#42楼 [楼主] 2008-04-22 08:11 王孟军!      
@狼Robot
象QQ一样,实现起来有点难道,
有时间交流...

  回复  引用  查看    

#43楼  2008-04-22 09:20 狼Robot      
我说下我的做法吧.

我的用户表一般会有这3个字段:登录次数,登录时间,登录IP.

我在保存登录用户信息的时候,一般会记一个用户名/ID,另外再记一个验证字符串,这个验证字符串的组成就包含了登录时间.

当用户同时在另一个位置登录时,登录时间肯定是变了的,这个时候,前一个登录用户的验证字符串中包含的时间就跟数据库中用户的登录时间对不上了,那这样就可以提示前一个登录的用户下线了.
  回复  引用  查看    

#44楼 [楼主] 2008-04-22 11:57 王孟军!      
@狼Robot
怎样让 前一个下线?
假设是基于Form的验证

  回复  引用  查看    

#45楼  2008-04-22 13:14 狼Robot      
楼主有没有即时通讯?QQ?MSN?
  回复  引用  查看    

#46楼 [楼主] 2008-04-22 13:45 王孟军!      
@狼Robot
QQ
258497297
  回复  引用  查看