关于SESSION的总结

来源:互联网 发布:nginx 反向代理 iis 编辑:程序博客网 时间:2024/04/30 18:18


前一阵子完善债权管理系统时涉及到防止用户重复提交的问题,我利用了SESSION来达到这样的目的,中间有一些曲折,经常丢失会话状态,昨天利用时间搜集了一些资料,进行学习和总结。主要如下:


一、什么是“会话(Session)



定义:session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息,比如打电话时从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个session


session一词与网络协议相关联时,它又往往隐含了“面向连接”和/或“保持状态”这样两个含义,“面向连接”指的是在通信双方在通信之前要先建立一个通信的渠道,比如打电话,直到对方接了电话通信才能开始,与此相对的是写信,在你把信发出去的时候你并不能确认对方的地址是否正确,通信渠道不一定能建立,但对发信人来说,通信已经开始了。“保持状态”则是指通信的一方能够把一系列的消息关联起来,使得消息之间可以互相依赖,比如一个服务员能够认出再次光临的老顾客并且记得上次这个顾客还欠店里一块钱。这一类的例子有“一个TCP session”或者“一个POP3 session”。


而到了web服务器蓬勃发展的时代,sessionweb开发语境下的语义又有了新的扩展,它的含义是指一类用来在客户端与服务器之间保持状态的解决方案。有时候session也用来指这种解决方案的存储结构,如“把xxx保存在session里”。由于各种用于web开发的语言在一定程度上都提供了对这种解决方案的支持,所以在某种特定语言的语境下,session也被用来指代该语言的解决方案,比如经常把Java里提供的javax.servlet.http.HttpSession简称为session


二、HTTP协议与状态保持


HTTP协议本身是无状态的,这与HTTP协议本来的目的是相符的,客户端只需要简单的向服务器请求下载某些文件,无论是客户端还是服务器都没有必要纪录彼此过去的行为,每一次请求之间都是独立的,好比一个顾客和一个自动售货机或者一个普通的(非会员制)大卖场之间的关系一样。


然而聪明的人们很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用,就像给有线电视加上点播功能一样。这种需求一方面迫使HTML逐步添加了表单、脚本、DOM等客户端行为,另一方面在服务器端则出现了CGI规范以响应客户端的动态请求,作为传输载体的HTTP协议也添加了文件上载、cookie这些特性。其中cookie的作用就是为了解决HTTP协议无状态的缺陷所作出的努力。至于后来出现的session机制则是又一种在客户端与服务器之间保持状态的解决方案。


让我们用几个例子来描述一下cookiesession机制之间的区别与联系。笔者曾经常去的一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠,然而一次性消费5杯咖啡的机会微乎其微,这时就需要某种方式来纪录某位顾客的消费数量。想象一下其实也无外乎下面的几种方案:


1、该店的店员很厉害,能记住每位顾客的消费数量,只要顾客一走进咖啡店,店员就知道该怎么对待了。这种做法就是协议本身支持状态。


2、发给顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每次消费时,如果顾客出示这张卡片,则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。


3、发给顾客一张会员卡,除了卡号之外什么信息也不纪录,每次消费时,如果顾客出示该卡片,则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。


由于HTTP协议是无状态的,而出于种种考虑也不希望使之成为有状态的,因此,后面两种方案就成为现实的选择。具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但实际上它还有其他选择。


三、理解cookie机制


cookie机制的基本原理就如上面的例子一样简单,但是还有几个问题需要解决:“会员卡”如何分发;“会员卡”的内容;以及客户如何使用“会员卡”。


正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie


cookie的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示,如果某家分店还发行了自己的会员卡,那么进这家店的时候除了要出示麦当劳的会员卡,还要出示这家店的会员卡。


cookie的内容主要包括:名字,值,过期时间,路径和域。


其中域可以指定某一个域比如.google.com,相当于总店招牌,比如宝洁公司,也可以指定一个域下的具体某台机器比如www.google.com或者froogle.google.com,可以用飘柔来做比。


路径就是跟在域名后面的URL路径,比如/或者/foo等等,可以用某飘柔专柜做比。路径与域合在一起就构成了cookie的作用范围。


如果不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。


存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。对于IE,在一个打开的窗口上按Ctrl-N(或者从文件菜单)打开的窗口可以与原窗口共享,而使用其他方式新开的IE进程则不能共享已经打开的窗口的内存cookie;对于Mozilla Firefox0.8,所有的进程和标签页都可以共享同样的cookie。一般来说是用javascriptwindow.open打开的窗口会与原窗口共享内存cookie(也有例外,后面谈到)。浏览器对于会话cookie的这种只认cookie不认人的处理方式经常给采用session机制的web应用程序开发者造成很大的困扰。


下面就是一个goolge设置cookie的响应头的例子


HTTP/1.1 302 Found


Location: http://www.google.com/intl/zh-CN/


Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com


Content-Type: text/html


浏览器在再次访问goolge的资源时自动向外发送cookie


四、理解session机制


session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。


当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为session id,如果已包含一个session id则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session idsession id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。


保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID,而。比如weblogic对于web应用程序生成的cookieJSESSIONID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是JSESSIONID


由于cookie可以被人为的禁止,必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写,另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。


在谈论session机制的时候,常常听到这样一种误解“只要关闭浏览器,session就消失了”。其实可以想象一下会员卡的例子,除非顾客主动对店家提出销卡,否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的,除非程序通知服务器删除一个session,否则服务器会一直保留,程序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个session id就消失了,再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够找到原来的session


恰恰是由于关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把session删除以节省存储空间。


五、小结


1session在何时被创建


一个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序创设sessionsession才被创建。由于session会消耗内存资源,因此,如果不打算使用session,应该在所有的开发语言中关闭它。


2session何时被删除


综合前面的讨论,session在下列情况下被删除a.程序关闭session;或b.距离上一次收到客户端发送的session id时间间隔超过了session的超时设置;或c.服务器进程被停止(非持久session


3、如何做到在浏览器关闭时删除session


严格的讲,做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作,然后向服务器发送一个请求来删除session,但是这样的资源占用属于得不偿失,而且对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。


4、如何才能正确的应付客户端禁止cookie的可能性


对所有的URL使用URL重写,包括超链接,formaction,和重定向的URL,具体做法参见http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770


5、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session


参见第三小节对cookie的讨论,对session来说是只认id不认人,因此不同的浏览器,不同的窗口打开方式以及不同的cookie存储方式都会对这个问题的答案有影响。


6、如何防止用户打开两个浏览器窗口操作导致的session混乱


这个问题与防止表单多次提交是类似的,可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端,同时保存在session里,客户端提交表单时必须把这个id也返回服务器,程序首先比较返回的id与保存在session里的值是否一致,如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表示层模式的部分。需要注意的是对于使用javascript window.open打开的窗口,一般不设置这个id,或者使用单独的id,以防主窗口无法操作,建议不要再window.open打开的窗口里做修改操作,这样就可以不用设置。


7、为什么session不见了


排除session正常失效的因素之外,服务器本身的可能性应该是微乎其微的;浏览器插件的可能性次之;理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。


出现这一问题的大部分原因都是程序的错误,最常见的就是在一个应用程序中去访问另外一个应用程序。我们在后面将讨论这个问题。


六、session无故丢失的原因


在进行开发的过程中,经常会莫名其妙地遇到SESSION丢失的情况,尤其是在IIS6的情况下,总结起来可能有以下几种情况:


1、进程重启 Asp.net默认配置会话是在进程内,所以Web.Config文件中关于Session的设定如下:


<sessionState mode='InProc' stateConnectionString='tcpip=127.0.0.1:42424' sqlConnectionString='data source=127.0.0.1;Trusted_Connection=yes' cookieless='true' timeout='60'/>


sessionState标签中有个属性mode,它可以有3种取值:InProc、StateServer、SQLServer(大小写敏感)。默认情况下是InProc,也就是将Session保存在进程内(IIS5是aspnet_wp.exe,而IIS6是W3wp.exe),这个进程不稳定,在某些事件发生时,进程会重起,所以造成了存储在该进程内的Session丢失。


哪些情况下该进程会重起呢?微软的一篇文章告诉了我们:


1、配置文件中processModel标签的memoryLimit属性


2、Global.asax或者Web.config文件被更改


3、Bin文件夹中的Web程序(DLL)被修改


4、杀毒软件扫描了一些.config文件。


 


解决办法:sessionState标签中mode属性可以有三个取值,除了InProc之外,还可以为StateServer、SQLServer。这两种存Session的方法都是进程外的,所以当aspnet_wp.exe重起的时候,不会影响到Session。


配置说明:


必选属性


属性 选项 说明


mode 指定在哪里存储会话状态。


Off 指示会话状态未启用。


InProc 指示在本地存储会话状态。


StateServer 指示在远程计算机上存储会话状态。


SQLServer 指示在 SQL Server 上存储会话状态。


可选属性


属性 选项 说明


cookieless 指定不具有 Cookie 的会话是否应用于标识客户端会话。


true 指示应使用不具有 Cookie 的会话。


false 指示不应使用没有 Cookie 的会话。默认值为 false。


timeout 指定在放弃一个会话前该会话可以处于空闲状态的分钟数。默认值为 20。


stateConnectionString 指定远程存储会话状态的服务器名称和端口。例如“tcpip=127.0.0.1:42424”。当 mode 为 StateServer 时该属性是必需的。


sqlConnectionString 为 SQL Server 指定连接字符串。例如“data source=localhost;Integrated Security=SSPI;Initial Catalog=northwind”。当 mode 为 SQLServer 时该属性是必需的。


stateNetworkTimeout 在使用 StateServer 模式存储会话状态时,指定在放弃会话之前 Web 服务器和状态服务器之间的 TCP/IP 网络连接空闲的时间(以秒为单位)。默认值为 10。


 


将mode设定为StateServer。StateServer是本机的一个服务,可以在系统服务里看到服务名为ASP.NET State Service的服务,默认情况是不启动的。当我们设定mode为StateServer之后,请手工将该服务启动。这样,我们就能利用本机的StateService来存储Session了,除非电脑重启或者StateService崩掉,否则Session是不会丢的(因Session超时被丢弃是正常的)。


除此之外,我们还可以将Session通过其他电脑的StateService来保存。具体的修改是这样的。同样还在sessionState标签中,有个stateConnectionString='tcpip=127.0.0.1:42424'属性,其中有个ip地址,默认为本机(127.0.0.1),你可以将其改成你所知的运行了StateService服务的电脑IP,这样就可以实现位于不同电脑上的Asp.net程序互通Session了。


如果你有更高的要求,需要在服务期重启时Session也不丢失,可以考虑将mode设定成SQLServer,同样需要修改sqlConnectionString属性。


在运行 SQL Server 的计算机(它将存储会话状态)上运行 InstallSqlState.sql(默认的安装位置为 <驱动器>:/systemroot/Microsoft.NET/Framework/version)。这将创建一个名为 ASPState 的数据库,该数据库具有新的存储过程并且在 TempDB 数据库中具有 ASPStateTempApplications 表和 ASPStateTempSessions 表。


在应用程序的 Web.config 文件中,设置 mode=SQLServer 并设置 sqlConnectionString 属性。例如,sqlConnectionString="data source=localhost;Integrated Security=SSPI;Initial Catalog=northwind"。


在使用StateServer或者SQLServer存储Session时,所有需要保存到Session的对象除了基本数据类型(默认的数据类型,如int、string等)外,都必须序列化。只需将[Serializable]标签放到要序列化的类前就可以了。


如:


[Serializable]


public class MyClass


{


    ......


}


注意:当在负载平衡的 Web 场环境中运行 ASP.NET Web 应用程序时,一定要使用 SqlServer 或 StateServer 会话状态模式。


 


2、有些杀病毒软件会去扫描您的Web.Config文件,会导致Session丢失。


解决办法:设置杀毒软件,排除对指定文件的扫描。


3、使用了ACCESS数据库,而且数据库是放在bin目录中的。


解决方法是不要放会更新的文件在BIN目录中。


4、在第一个页面置了SESSION,然后REDIRECT去第二个页面。


解决方法是在REDIRECT中设置endResponse为FALSE。


5、IIS6默认设置下对每个运行在默认应用池中的工作进程都会经过20多个小时后自动回收该进程,造成保存在该进程中的session丢失。


解决办法: 修改配置,设置为不定时自动回收该工作进程,比如设置为当超出占用现有物理内存60%后自动回收该进程。通过使用默认应用程序池,可以确保多个应用程序间互相隔离,保证由于一个应用程序的崩溃不会影响另外的Web应用程序。还可以使一个独立的应用程序运行在一个指定的用户帐号特权之下。如果使用StateServer方式或者Sql Server数据库方式来保存Session,则不受该设置的影响。


6、文件夹选项中,如果没有打开“在单独的进程中打开文件夹窗口”,一旦新建一个窗口,系统可能认为是新的Session会话,而无法访问原来的Session。


解决办法:打开该选项。


7、检网络编码是否存在问题导致session被清除。


8、检查Session的过期时间设置。在IIS上有相应的会话过期时间选项卡,默认时间是20分钟,可以修改成更长的时间。或者通过编程语言设置,如Session.timeout=60。


9、客户端是否启用了COOKIE。虽然SESSION数据保存在服务器端,但SESSIONID总是保存在客户端的,它依赖于客户端的COOKIE,除非开发者采用URL重写的方法,所以要检查客户端的浏览器是否启用了COOKIE。


由于cookie可以被人为的禁止,必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。有两种机制可以实现这样的目的:URL重写和隐藏表单。


URL重写,就是把session id直接附加在URL路径的后面,附加方式也有两种,一种是作为URL路径的附加信息,表现形式为http://...../xxx;jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764


另一种是作为查询字符串附加在URL后面,表现形式为http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764


这两种方式对于用户来说是没有区别的,只是服务器在解析的时候处理的方式不同,采用第一种方式也有利于把session id的信息和正常程序参数区分开来。


为了在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id


表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。