现在的位置: 首页 > 综合 > 正文

手动模拟Session机制的原理

2014年05月24日 ⁄ 综合 ⁄ 共 7341字 ⁄ 字号 评论关闭

 

  概要的说,.NET内置的Session其实是借助于Cookie机制实现的。下面先解释为什么这么说,然后通过对比一个.NET内置的Session实例和一个手动模拟的Session机制(会借助于Cookie)来进一步验证。


为什么这么说

(1)首先,由于需要用到一些必要的Cookie相关知识,这里简要介绍:

  Ø  Cookie存放于客户端,所以东西是在别人手中,所以就不会很安全

  Ø  由于浏览器可以设置清除、禁用Cookie,所以不可以将不可或缺的东西放到Cookie中,只能将可有可无的东西放到Cookie中。

  Ø  服务器将Cookie写到客户端,之后当客户访问服务器(浏览器不变)该站点的任何内容时,request的信息中都会带上所有的Cookie的值。

  Ø  Cookie不能夸不同品牌的浏览器,互相访问(比如Chrom写的Cookie,IE不能使用)。

  Ø  若不设置Cookie过期时间(Expires),则称为会话Cookie,表示这个Cookie的生命期为浏览器会话期间,关闭浏览器窗口,Cookie就消失,会话Cookie一般不存储在硬盘上而是保存在内存里;若设置了过期时间,浏览器就会把Cookie保存到硬盘上,关闭后再次打开浏览器,这些Cookie仍然有效直到超过设定的过期时间,或许应该这么说,如果设置了Expires,则这个Cookie的最长生命周期为Expires的值,如果Cookie已存满(个数和容量大小都有限制)则即使没到期,浏览器也会给删除。

(2)下面开始解释.NET的Session存放原理:

  上面介绍了,ASP.NET为了保持和传递状态,一种机制就是上面的Cookie。写入Cookie的数据是存放在客户端的,不会很安全,所以需要将数据从客户端移到服务器端,即所谓的Session。但Session并不是将单纯的数据直接保存到服务器内存中,而是建立一个类似散列表的id-value映射表结构,value即指我们要保存的数据,id是由ASP.NET自动生成的一个编号(ASP.NET_SessionId),即:每当我们要写数据到Session时,ASP.NET就会生成一个编号(ASP.NET_SessionId,它是一个既不会重复,又不容易被找到规律以仿造的字符串),然后将编号作为数据的索引把二者进行绑定,然后会将这个编号以Cookie的形式保存到客户端,而将数据保留到服务器端的内存中,这样在客户端请求Session的同时会将Cookie中的ASP.NET_SessionId一同发送到服务器端,服务器就可以根据这个编号在自己的数据中进行检索,进而取出它的value值。这就是ASP.NET中的Session机制原理。可参考下面的草图:


  


实例说明-分析

  下面做这么一个小例子:在登陆的时候将用户名写入Session,然后才能浏览站点内部的其它页面。如果没有登录,直接请求站点内部的page,则会检测Session,如果为空则跳转到登陆页面。

  分别用.NET内置的Session实例和手动模拟的Session机制(会借助于Cookie)来实现。

  当然,处理页面的很少的部分仍然采用NVelocity,所以首先添加它的DLL,并引用(参考前面两篇文章)。然后添加封装好的”渲染”代码, 如下的CommonHelper类:

    public class CommonHelper
    {
        /// <summary>
        /// 用data数据填充templateName模板,渲染生成html,返回
        /// </summary>
        /// <param name="templateName"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public static string RenderHtml(string templateName, object data)
        {
            VelocityEngine vltEngine = new VelocityEngine();
            vltEngine.SetProperty(RuntimeConstants.RESOURCE_LOADER, "file");
            vltEngine.SetProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, System.Web.Hosting.HostingEnvironment.MapPath("~/templates"));//模板文件所在的文件夹
            vltEngine.Init();

            VelocityContext vltContext = new VelocityContext();
            vltContext.Put("TemData", data);//设置参数,在模板中可以通过$data来引用

            Template vltTemplate = vltEngine.GetTemplate(templateName);
            System.IO.StringWriter vltWriter = new System.IO.StringWriter();
            vltTemplate.Merge(vltContext, vltWriter);

            string html = vltWriter.GetStringBuilder().ToString();
            return html;
        }
    }

  .NET内置的Session实例

    先用我们平时用的Session实现上例,看看它的保存机制。

    添加三个文件:Login3.html、Login3.ashx(做登录处理);TestLogin3.ashx(作为站点内部的其它页面)。代码如下:

    Login3.html源码及运行界面:   

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>.net内置的Session测试</title>
</head>
<body>
    <form action="../Login3.ashx" method="get">
        用户名:<input type="text" name="username" />
        <br />
        密 码:<input type="text" name="password"/>
        <br />
        <input type="submit" name="login" value="提交" />
    </form>
</body>
</html>

    Login3.ashx:    

    public class Login3 : IHttpHandler, IRequiresSessionState
    {

        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/html";
            string UserName = context.Request["username"];
            string PassWord = context.Request["password"];
            //登陆时,判断用户名是否正确(这里设置为admin),正确则保存用户名到Session,并跳转到TestLogin3.ashx
            //否则清空输入框,页面不变
            if (UserName == "admin")
            {
                //将用户名保存到.net内置Session中
                context.Session["username"] = UserName;
                //保存好用户名后进入TestLogin3.ashx页面
                context.Response.Redirect("TestLogin3.ashx");
            }
            else
            {
                string html = CommonHelper.RenderHtml("Login3.html", null);
                context.Response.Write(html);
            }
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }

   

   TestLogin3.ashx:    

   public class TestLogin3 : IHttpHandler,IRequiresSessionState
    {

        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/html";
            //如果Session为null,则跳转到登陆页
            if (context.Session == null)
            {
                context.Response.Redirect("Login3.ashx");

            }
            else
            { 
                string username=(string)context.Session["username"];
                //如果Session中的username对象为空或不存在则跳转到登陆页,否则将它显示出来
                if (string.IsNullOrEmpty(username))
                {
                    context.Response.Redirect("Login3.ashx");
                }
                else
                {
                    context.Response.Write(username);
                }
            }
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }

    如下为填写用户名为admin点击“登录”捕获的数据,请求的是Login3.ashx,里面的处理是将用户名保存写入到了Session中,而可以看到响应标头中,有一个写Cookie的过程,而且Cookie的内容为ASP.NET_SessionId。


    


    当Login3.ashx验证用户名正确后,立刻跳转到TestLogin3.ashx,如下图为跳转时捕获的数据,可以看到在请求的标头中携带有Cookie,而Cookie的值正是上面写Session时保存到客户端的ASP.NET_SessionId。


    


    TestLogin3中的处理是,验证.Session["username"]是否存在,存在则取其值并显示。而验证是否存在和取其值的过程都是根据请求中的Cookie中的ASP.NET_SessionId来做的。

    上例即可说明Session机制的原理。下面通过手动用Cookie来模拟Session来做相同的实例,以便大家理解的更深刻。

  手动用Cookie来模拟Session机制

    添加四个文件:Login2.html、Login2.ashx(做登录处理);TestLogin2.ashx(作为站点内部的其它页面)、SessionMgr.cs(模拟.net内部对Session对象的封装)。代码如下:

    Login2.html与上面的Login3.html代码相同,只有title不同,代码:   

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>模拟.net内置的Session机制</title>
</head>
<body>
    <form action="../Login2.ashx" method="get">
        用户名:<input type="text" name="username" />
        <br />
        密 码:<input type="text" name="password"/>
        <br />
        <input type="submit" name="login" value="提交" />
    </form>
</body>
</html>

    SessionMgr.cs:    

  //自定义Session的操作类
    public class SessionMgr
    {
        //Static在.net framework运行的时候一直存在
        //自定义一个Dictionary类型的对象,来模拟.net内置的Session对象
        private static Dictionary<Guid,string> testSession=new Dictionary<Guid,string>();
        //写(自定义)session
        public static void WriteSession(Guid id, string value)
        {
            testSession[id] = value;
        }
        //判断是否已经存在编号为id的(自定义)session
        public static bool IsWriteSession(Guid id)
        {
            return testSession.Keys.Contains(id);
        }
        //获取编号为id的(自定义)session的value值
        public static string Get(Guid id)
        {
            return testSession[id];
        }
    }

    Login2.ashx:  

  public class Login2 : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/html";
            //取登陆时填写的用户名、密码
            string UserName=context.Request["username"];
            string PassWord = context.Request["password"];
            //登陆时,判断用户名是否正确(这里设置为admin),正确则保存用户名到Session,并跳转到TestLogin3.ashx
            //否则清空输入框,页面不变
            if (UserName == "admin")
            {
                //关键点
                Guid id = Guid.NewGuid();//生成一个session编号,Guid可生成一个全球唯一的编码,来模拟.net内置Session的ASP.NET_SessionId
                SessionMgr.WriteSession(id, UserName);//写入(自定义的)session,将UserName与生成的id绑定(借助于SessionMgr类)
                HttpCookie cookie1 = new HttpCookie("sessionid", id.ToString());//只将id保存到cookie中,并未保存对应的数据(UserName)
                context.Response.SetCookie(cookie1);
                context.Response.Redirect("TestLogin2.ashx");
            }
            else
            {
                string html = CommonHelper.RenderHtml("Login2.html", null);
                context.Response.Write(html);
            }
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }

    TestLogin2.ashx:  

    public class TestLogin2 : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/html";
            HttpCookie cookie = context.Request.Cookies["sessionid"];
            //请求此页时,如果cookie为null则跳转到登陆页
            //如果不为空,则取出cookie的值作为一个Guid编号
            if (cookie == null)
            {
                context.Response.Redirect("Login2.ashx");
            }
            else
            {
                Guid id = new Guid(cookie.Value);
                //判断服务器端的Dictionary中是否包含编号为id的对象
                //如果包含则取出其value,并显示
                //否则跳转到登陆页
                if (SessionMgr.IsWriteSession(id))
                {
                    string value = SessionMgr.Get(id);
                    context.Response.Write(value);
                }
                else
                {
                    context.Response.Write("<script>alert('没有登录!');</script>");
                    context.Response.Redirect("Login2.ashx");
                }
            }
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }

    上例中用一个Dictionary<id,value>对象来模拟Session对象,用它的id(Guid)来模拟.NET自动生成的编号(ASP.NET_SessionId),用它的value来模拟要写入Session的数据。


浏览器禁用Cookie问题

  上面介绍Cookie时谈到,浏览器可以设置清除、禁用Cookie。而刚刚谈到的Session机制是依赖于Cookie的。那么按上面的意思,当禁用Cookie时就意味着ASP.NET_SessionId 无法回传服务器,Session也就无法使用。是否是这样?答案肯定是否,应对这种情况,经常用两种方法:

  URL地址重写:

    即将该用户Session的id信息重写到URL地址中。服务器能够解析重写后的URL获取Session的id。这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。

  表单隐藏字段

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

    

总结

  之前写过相关的文章,完全是理论的学习,现在所有的东西都可以通过写代码和浏览器调试来验证自己的想法。上面是目前阶段自己的理解,希望能帮助大家理解ASP.NET的Session机制。

 

抱歉!评论已关闭.