Session高级应用之简介
一、 把Session存储在独立的进程中
由此看来,Session能存储任意对象,是这样吗?现在得出这个结论还太早了一点,因为我们并没有实践过StateServer和SqlServer模式的Session。要把Session存储在Windows服务进程中需要进行以下几个步骤。
第1步是打开状态服务。依次打开“控制面板”→“管理工具”→“服务”命令,找到ASP.NET状态服务一项,右键单击服务选择启动,如图2-2所示。
图2-2 启动ASP.NET状态服务
如果你正式决定使用状态服务存储Session前,别忘记修改服务为自启动(在操作系统重启后服务能自己启动)以免忘记启动服务而造成网站Session不能使用,如图2-3所示,双击服务把服务的启动类型设置为自动。
图2-3 修改服务启动类型为自动
服务正常启动后可以观察任务管理器的进程页,其中的aspnet_state.exe进程就是状态服务进程,如图2-4所示。
图2-4
观察任务管理器的进程页
第2步,在system.web节点中加入:
<sessionState mode="StateServer" stateConnectionString="tcpip=127.0.0.1:42424"
stateNetworkTimeout="20"></sessionState>
stateConnectionString表示状态服务器的通信地址(IP:服务端口号)。由于我们现在在本机进行测试,这里设置成本机地址127.0.0.1。状态服务默认的监听端口为42422。当然,您也可以通过修改注册表来修改状态服务的端口号。
1.在运行中输入regedit启动注册表编辑器。
2.依次打开HKEY_LOCAL_MACHINE"SYSTEM"CurrentControlSet"Services"aspnet_state"Parameters节点,双击Port选项,如图2-5所示。
选择基数为十进制,然后输入一个端口号即可。stateNetworkTimeout属性表示从状态服务器请求Session数据最长的时间,默认为10秒,如果网络连接不是很好,请把这个数字适当设置得大一点。
第3步打开页面,单击“写入Session”按钮,系统会报错,如图2-6所示。
图2-5 修改状态服务端口号
图2-6 向StateServer默认的Session中写入自定义类出错
提示已经说得很清楚了,只有把对象标注为可序列化后才能在服务中进行存储。什么是序列化呢?序列化是指将对象实例的状态存储到存储媒体的过程。在此过程中,先将对象的公共字段和私有字段以及类的名称转换为字节流,然后再把字节流写入数据流。在随后对对象进行反序列化时,将创建出与原对象完全相同的副本。要使一个类可序列化,最简单的方法是使用 Serializable 属性对它进行标记。
[Serializable]
class MyUser
{
public string sUserName;
public int iAage;
public override string ToString()
{
return string.Format("姓名:{0},年龄:{1}", sUserName, iAage);
}
}
第4步现在重新打开页面进行测试,得到的结果和使用InProc模式是一样的。
二、 把Session存储在数据库中
要把Session存储在SqlServer中,基本上也是这么几个步骤。
1.在命令行窗口输入cmd并在命令行中运行如下命令。
C:"WINDOWS"Microsoft.NET"Framework"v2.0.50727"aspnet_regsql.exe -S ."SqlExpress -E –ssadd
其中C:"Windows用你自己Windows的目录代替,v2.0.50727用你安装的2.0框架的版本号代替。-S指定SqlServer服务器地址,-E表示采用信任连接,-ssadd表示为SqlServer服务器添加状态服务的支持。操作结束后,你可以使用IDE的服务器资源管理器连接SqlExpress数据库,可以看到多了一个ASPState数据库,但是奇怪的是数据库中没有任何表却有很多存储过程,如图2-7所示。
其实,所有Session的数据都存放在了tempdb数据库内,如图2-8所示。
图2-7 使用服务器资源管理器浏览ASPState数据库
图2-8 存放Session数据的tempdb数据库
其实,aspnet_regsql.exe有一个-sstype参数可以用来指定Session的内容和操作的存储过程存放的表。由于篇幅关系,在这里就不详细介绍了,读者可以使用aspnet_regsql.exe/?来浏览程序详细的使用方式。
2.打开Web.config文件,修改前面建立的sessionState节点。
<sessionState mode="SQLServer" sqlConnectionString="server=(local)"SQLEXPRESS;
Trusted_Connection=True" sqlCommandTimeout="60"></sessionState>
为sqlConnectionString属性指定以前一直用的连接字符串,唯一不同的是不需要再指定数据表的名字了。sqlCommandTimeout属性表示允许执行Sql命令最长的时间,默认为30秒,可以根据自己的需要适当调整这个数字。最后,重新打开页面进行测试,得到的结果和使用InProc模式是一样的(同样你需要确保在自定义类前标注了[Serializable]),不过我们能感到速度有些慢了,毕竟数据是从数据库中进行读取或保存的,而且在使用前还需要经过序列化和反序列化操作。
因此Session能存储的类型为: 对于InProc模式是一切类型,而对于StateServer和SqlServer模式是一切可以序列化的类型。
三、 Session的使用范围与大小限制
那么,会话状态使用的范围和大小限制又是怎么样的呢?我们可以分析一下图2-8,系统使用两个表来存储Session的状态。其中有一个ASPStateTempApplication表,用来存储Session所在的应用程序,一定程度上反映了Session是不能跨应用程序的。举例来说,我们在计算机上建立了两个网站,同时都使用Session[“UserName”]来保存登录的用户名,一个网站的用户登录后,另一个网站直接访问Session[“UserName”]是取不到任何值的。那么,Session是否可以跨用户呢?通过前面的分析我们知道,肯定是不行的,Session通过SessionID来区分用户,一般来说SessionID是不可能出现重复的现象,也就是说Session一般是不会“串号”的。既然页面每次提交的时候都会附加上当前用户的SessionID,那么Session应该是可以跨页面的,也就是说一个网站中所有的页面都使用同一份Session。你可以自己来做个试验,请读者打开刚才那个页面,然后按Ctrl+N组合键再打开第二个同样的页面,单击第一个页面中的“写入Session”按钮,单击第二个页面中的“读取Session”按钮,可以发现Session的值被正确读出了。第三个问题的答案有了。
· Session状态使用的范围:使用同一个客户端(浏览器实例)访问同一个应用程序的所有页面。
我们再来做一个试验,看看Session的容量有多大,在测试以前请修改Web.config,把Session设置为StateServer模式。然后,把写入Session的代码修改成如下(别忘记using System.Data.SqlCient):
DataSet ds = new DataSet();
using (SqlConnection conn = new SqlConnection(@"server=(local)"SQLEXPRESS;database=Forum;
Trusted_Connection=True"))
{
SqlDataAdapter da = new SqlDataAdapter("select * from tbUser;select * from tbBoard;
select * from tbTopic;", conn);
da.Fill(ds);
}
ArrayList al = new ArrayList();
for(int i = 0;i<10000000;i++)
al.Add(ds);
Session["LargeData"] = al;
我们把包含三个表的DataSet重复加入ArrayList中1000万次。由于这些表几乎每个表只有几条记录,这样可以模拟大数据量的情况。启动页面,单击“写入Session”按钮后可以发现,Windows服务进程一下子占用了多达70MB的内存,如图2-9所示。
图2-9 把大量数据存放到Session中
Session对于网站和用户是独立的,试想一下,如果服务器上有两个网站,每个网站的在线人数是100人,那么占用内存就要14G。是不是很恐怖的数字?因此,虽然Session的大小没有限制,但是我们千万不能滥用Session。笔者推荐你在Session中存储少于100K的数据。
· 如果你使用InProc模式的Session,存储过多的数据会导致IIS进程被回收,引发Session不断丢失。
· 如果你使用StateServer存储Session,那么数据在存入Session以前需要进行序列化,序列化会消耗大量的CPU资源。
· 如果你使用SqlServer模式的Session,数据不但要序列化而且还是存储在磁盘上,更不适合存储大量数据。
四、 Session的生命周期
在了解了Session中存储的数据无大小限制后,我们可能要更多地关心Session的生命周期了。我们已经知道,Session是在用户第一次访问网站的时候创建的,那么Session是什么时候销毁的呢?Session使用一种平滑超时的技术来控制何时销毁Session。默认情况下,Session的超时时间(Timeout)是20分钟,用户保持连续20分钟不访问网站,则Session被收回,如果在这20分钟内用户又访问了一次页面,那么20分钟就重新计时了,也就是说,这个超时是连续不访问的超时时间,而不是第一次访问后20分钟必过时。这个超时时间同样也可以通过调整Web.config文件进行修改:
<sessionState timeout="30"></sessionState>
当然你也可以在程序中进行设置:
Session.Timeout = "30";
一旦Session超时,Session中的数据将被回收,如果再使用Session系统,将给你分配一个新的SessionID。本节一开始我们就介绍了可以在URL中存储SessionID,现在请你配置Web.config文件,设置Session超时时间为1分钟,SessionID在URl中存放。打开页面后单击“写入Session”按钮,过1分钟再次单击按钮并观察SessionID是否变化。
<sessionState timeout="1" cookieless="true"></sessionState>
如图2-10所示,SessionID的确发生了变化。
图2-10 超时后SessionID发生变化
不过,你可别太相信Session的Timeout属性,如果你把它设置为24小时,则很难相信24小时之后用户的Session还在。Session是否存在,不仅仅依赖于Timeout属性,以下的情况都可能引起Session丢失(所谓丢失就是在超时以前原来的Session无效)。
· bin目录中的文件被改写。asp.net有一种机制,为了保证dll重新编译之后,系统正常运行,它会重新启动一次网站进程,这时就会导致Session丢失,所以如果有access数据库位于bin目录,或者有其他文件被系统改写,就会导致Session丢失。
· SessionID丢失或者无效。如果你在URL中存储SessionID,但是使用了绝对地址重定向网站导致URL中的SessionID丢失,那么原来的Session将失效。如果你在Cookie中存储SessionID,那么客户端禁用Cookie或者Cookie达到了IE中Cookie数量的限制(每个域20个),那么Session将无效。
· 如果使用InProc的Session,那么IIS重启将会丢失Session。同理,如果使用StateServer的Session,服务器重新启动Session也会丢失。
一般来说,如果在IIS中存储Session而且Session的Timeout设置得比较长,再加上Session中存储大量的数据,非常容易发生Session丢失的问题。
最后,Session的安全性怎么样呢?我们知道,Session中只有SessionID是存储在客户端的,并且在页面每次提交的过程中加入HTTP头发送给服务器。SessionID只是一个识别符,没有任何内容,真正的内容是存储在服务器上的。总的来说安全性还是可以的,不过笔者建议你不要使用cookieless和SqlServer模式的Session。把SessionID暴露在URL中,把内容存储在数据库中可能会发生攻击隐患。
五、遍历与销毁Session
Session虽然很方便,但是要用好Session还需要自己不断实践,根据自己网站的特点灵活使用各种模式的Session。关于使用程序访问Session,笔者还想补充两点。
· 如何遍历当前的Session集合。
System.Collections.IEnumerator SessionEnum = Session.Keys.GetEnumerator();
while (SessionEnum.MoveNext())
{
Response.Write(Session[SessionEnum.Current.ToString()].ToString() + "<br/>");
}
对于我们这个例子,输出和图2-1一样。如果你仅仅为了监视Session,也可以通过trace来获得详细信息。在Web.config的system.Web节点中添加:
<trace enabled="true" pageOutput="true"/>
打开页面后单击“写入Session”按钮,页面显示如图2-11所示。
图2-11 使用trace观察会话状态
· 如何立刻让Session失效。比如用户退出系统后,Session中保存的所有数据全部失效,可以使用以下代码来让Session失效。
Session.Abandon();
六、 Session的常见问题与总结
Session的基本知识就介绍到这里,现在再回头看第一节中的几个问题,你是否都能回答了呢?为了强化大家的概念,笔者就三种模式的Session进行了一个比较(假设都使用Cookie来存储SessionID)。
表12.1 三种模式的Session比较
|
InProc |
StateServer |
SQLServer |
存储物理位置 |
IIS进程(内存) |
Windows服务进程(内存) |
SQLServer数据库(磁盘) |
存储类型限制 |
无限制 |
可以序列化的类型 |
可以序列化的类型 |
存储大小限制 |
无限制 |
||
使用范围 |
当前请求上下文,对于每个用户独立 |
||
生命周期 |
第一次访问网站的时候创建Session超时后销毁 |
||
优点 |
性能比较高 |
Session不依赖Web服务器,不容易丢失 |
|
缺点 |
容易丢失 |
序列化与反序列化消耗CPU资源 |
序列化与反序列化消耗CPU资源,从磁盘读取Session比较慢 |
使用原则 |
不要存放大量数据 |
在使用Session的过程中你可能还会遇到很多奇怪的问题,结束本节之前笔者列出了几条常见的FAQ,供大家参考:
· 为什么每次请求的SessionID都不相同?
可能是没有在Session里面保存任何信息引起的,即程序中任何地方都没有使用Session。只有在Session中保存了内容后,Session才会和浏览器进行关联,此时的SessionID将不会再变化。
· 为什么当我设置cookieless为true后,在重定向的时候会丢失Session?
当使用cookieless时,你必须使用相对路径替换程序中的绝对路径,如果使用绝对路径,ASP.NET将无法在URL中保存SessionID。
· 有办法知道应用程序的Session在运行时占用了多少内存吗?
没有办法,你可以通过观察IIS进程(InProc模式)或者aspnet_state进程(StateServer模式)大致估计。
· 有没有可能知道整个网站使用Session的用户列表?
对于InProc模式和StateServer模式很难,对于SqlServer模式你可以查询存储Session的表进行尝试。
· 当页面中设了frameset,发现在每个frame中显示页面的SessionID在第一次请求时都不相同,为什么?
原因是你的frameset是放在一个HTML页面上而不是ASPX页面。在一般情况下,如果frameset是aspx页面,当你请求页面时,它首先将请求发送到Web服务器,此时已经获得了SessionID,接着浏览器会分别请求Frame中的其他页面,这样所有页面的SessionID就是一样的,就是FrameSet页面的SessionID。然而如果你使用HTML页面做FrameSet页面,第一个请求将是HTML页面,当该页面从服务器上返回时并没有任何Session产生,接着浏览器会请求Frame里面的页面,这样,这些页面都会产生自己的SessionID,所以在这种情况下就可能出现这种问题。当你重新刷新页面时,SessionID就会一样,并且是最后一个请求页面的SessionID。