zoukankan      html  css  js  c++  java
  • ASP.NET 中 Session 实现原理浅析 [1] 会话的建立流程

        HTTP 协议之所以能够获得如此大的成功,其设计实现的简洁性和无状态连接的高效率是很重要的原因。而为了在无状态的 HTTP 请求和有状态的客户端操作之间达到平衡,产生了服务器端会话 (Session) 的概念。客户端在连接到服务器后,就由 Web 服务器产生并维护一个客户端的会话;当客户端通过无状态 HTTP 协议再次连接到服务器时,服务器根据客户端提交的某种凭据,如 Cookie 或 URL 参数,将客户关联到某个会话上。这种思路在各种开发语言和开发环境中大量得到应用。
        在 ASP.NET 中,Web 应用程序和会话状态被分别进行维护,通过 HttpApplication 和 HttpSessionState 分离 Web 应用程序与会话的功能。应用程序层逻辑在 Global.asax 文件中实现,运行时编译成 System.Web.HttpApplication 的实例;会话则作为单独的 System.Web.SessionState.HttpSessionState 实例,由服务器统一为每个用户会话维护,通过 ASP.NET 页面编译成的 System.Web.UI.Page 对象子类的 Session 属性访问。关于 ASP.NET 中不同层次关系可参考我以前的一篇文章《.NET 1.1中预编译ASP.NET页面实现原理浅析 [1] 自动预编译机制浅析》,以下简称【文1】。

        ASP.NET 在处理客户端请求时,首先将根据客户端环境,生成一个 System.Web.HttpContext 对象,并将此对象作为执行上下文传递给后面的页面执行代码。
        在【文1】的分析中我们可以看到,HttpRuntime 在处理页面请求之前,根据 HttpWorkerRequest 中给出的环境,构造 HttpContext 对象,并以次对象作为参数从应用程序池中获取可用应用程序。简要代码如下:
    private void HttpRuntime.ProcessRequestInternal(HttpWorkerRequest wr)
    {
      
    // 构造 HTTP 调用上下文对象
      HttpContext ctxt = new HttpContext(wr, 0);

      
    //

      
    // 获取当前 Web 应用程序实例
      IHttpHandler handler = HttpApplicationFactory.GetApplicationInstance(ctxt);

      
    // 调用 handler 实际处理页面请求
    }



        HttpApplicationFactory 工厂内部维护了一个可用的应用程序实例缓冲池,用户降低应用程序对象构造的负荷。
        如果池中没有可用的应用程序对象实例,此对象工厂最终会调用 System.Web.HttpRuntime.CreateNonPublicInstance 方法构造新的应用程序实例,并调用其 InitInternal 方法初始化。详细步骤分析见【文1】
    internal static IHttpHandler HttpApplicationFactory.GetApplicationInstance(HttpContext ctxt)
    {
      
    // 处理定制应用程序
      
    //

      
    // 处理调试请求
      
    //

      
    // 判断是否需要初始化当前 HttpApplicationFactory 实例
      
    //

      
    // 获取 Web 应用程序实例
      return HttpApplicationFactory._theApplicationFactory.GetNormalApplicationInstance(ctxt);
    }


    private HttpApplication HttpApplicationFactory.GetNormalApplicationInstance(HttpContext context)
    {
      HttpApplication app 
    = null;

      
    // 尝试从已施放的 Web 应用程序实例队列中获取
      
    //

      
    if(app == null)
      
    {
         
    // 构造新的 Web 应用程序实例
        app = (HttpApplication)System.Web.HttpRuntime.CreateNonPublicInstance(this._theApplicationType);

        
    // 初始化 Web 应用程序实例
        app.InitInternal(context, this._state, this._eventHandlerMethods);
      }


      
    return app;
    }


        这里的 System.Web.HttpApplication.InitInternal 函数完成对应用程序对象的初始化工作,包括调用 HttpApplication.InitModules 函数初始化 HTTP 模块(后面将详细介绍),并将作为参数传入的 HttpContext 实例保存到 HttpApplication._context 字段中。而此 HTTP 上下文对象将被后面用于获取会话对象。
    public class HttpApplication : 
    {
      
    private HttpContext _context;
      
    private HttpSessionState _session;

      
    public HttpSessionState Session
      
    {
        
    get
        
    {
          HttpSessionState state 
    = null;
          
    if (this._session != null)
          
    {
            state 
    = this._session;
          }

          
    else if (this._context != null)
          
    {
            state 
    = this._context.Session;
          }

          
    if (state == null)
          
    {
            
    throw new HttpException(HttpRuntime.FormatResourceString("Session_not_available"[img]/images/wink.gif[/img]);
          }

          
    return state;
        }

      }

    }


        而在 ASP.NET 页面中获取会话的方法也是类似,都是通过 HttpContext 来完成的。
    public class Page : 
    {
      
    private HttpSessionState _session;
      
    private bool _sessionRetrieved;

      
    public virtual HttpSessionState Session
      
    {
        
    get
        
    {
          
    if (!this._sessionRetrieved)
          
    {
            
    this._sessionRetrieved = true;
            
    try
            
    {
              
    this._session = this.Context.Session;
            }

            
    catch (Exception)
            
    {
            }

          }

          
    if (this._session == null)
          
    {
            
    throw new HttpException(HttpRuntime.FormatResourceString("Session_not_enabled"[img]/images/wink.gif[/img]);
          }

          
    return this._session;
        }

      }

    }


        在 HttpContext 中,实际上是通过一个哈希表保存诸如会话对象之类信息的
    public sealed class HttpContext : 
    {
      
    private Hashtable _items;

      
    public IDictionary Items
      
    {
        
    get
        
    {
          
    if (this._items == null)
          
    {
            
    this._items = new Hashtable();
          }

          
    return this._items;
        }

      }


      
    public HttpSessionState Session
      
    {
        
    get
        
    {
          
    return ((HttpSessionState) this.Items["AspSession"]);
        }

      }

    }


        而 HttpContext.Session 所访问的又是哪儿来的呢?这就又需要回到我们前面提及的 HttpApplication.InitModules 函数。

        在 .NET 安装目录 Config 子目录下的 machine.config 定义了全局性的配置信息,而 HttpApplication 就是使用其中 system.web 一节的配置信息进行初始化的。
    <system.web>
        
    <httpModules>
          
    <add name="OutputCache" type="System.Web.Caching.OutputCacheModule" />
          
    <add name="Session" type="System.Web.SessionState.SessionStateModule" />
          
    <add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" />
          
    <add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />
          
    <add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule" />
          
    <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
          
    <add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" />
          
    <add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
        
    </httpModules>
    </system.web>

        httpModules 节点指定了 HttpApplication 需要初始化的模块列表,而在前面提到的 HttpApplication.InitModules 函数正式根据此列表进行初始化的
    private void HttpApplication.InitModules()
    {
      HttpModulesConfiguration cfgModules 
    = ((HttpModulesConfiguration) HttpContext.GetAppConfig("system.web/httpModules"[img]/images/wink.gif[/img]);

      
    if (cfgModules == null)
      
    {
        
    throw new HttpException(HttpRuntime.FormatResourceString("Missing_modules_config"[img]/images/wink.gif[/img]);
      }

      _moduleCollection 
    = cfgModules.CreateModules();

      
    for(int i = 0; i < _moduleCollection.Count; i++)
      
    {
        _moduleCollection[i].Init(
    this);
      }


      GlobalizationConfig cfgGlobal 
    = ((GlobalizationConfig) HttpContext.GetAppConfig("system.web/globalization"[img]/images/wink.gif[/img]);
      
    if (cfgGlobal != null)
      
    {
        _appLevelCulture 
    = cfgGlobal.Culture;
        _appLevelUICulture 
    = cfgGlobal.UICulture;
      }

    }


        Session 节点对于的 System.Web.SessionState.SessionStateModule 对象将被 HttpModulesConfiguration.CreateModules 方法构造,并调用其 Init 函数初始化。SessionStateModule 类实际上就是负责管理并创建会话,用户完全可以自行创建一个实现 IHttpModule 接口的类,实现会话的控制,如实现支持集群的状态同步等等。
        SessionStateModule.Init 方法主要负责 machine.config 文件中的 sessionState 配置,调用 SessionStateModule.InitModuleFromConfig 方法建立相应的会话管理器。
    <system.web>
        
    <sessionState mode="InProc"
                      stateConnectionString
    ="tcpip=127.0.0.1:42424"
                      stateNetworkTimeout
    ="10"
                      sqlConnectionString
    ="data source=127.0.0.1;Integrated Security=SSPI"
                      cookieless
    ="false"
                      timeout
    ="20" />
    </system.web>

        sessionState 的使用方法请参加 MSDN 中相关介绍和 INFO: ASP.NET State Management Overview。关于不同 mode 的会话管理的话题我们后面再讨论,先继续来看会话的建立过程。

        在从 machine.config 文件中读取配置信息后,InitModuleFromConfig 方法会向 HttpApplication 实例注册几个会话管理事件处理函数,负责在应用程序合适的情况下维护会话状态。
    private void SessionStateModule.InitModuleFromConfig(HttpApplication app,
        SessionStateSectionHandler.Config config, 
    bool configInit)
    {
      
    // 处理不使用 Cookie 的情况
      
    //

      app.AddOnAcquireRequestStateAsync(
    new BeginEventHandler(this.BeginAcquireState),
                                        
    new EndEventHandler(this.EndAcquireState));
      app.ReleaseRequestState 
    += new EventHandler(this.OnReleaseState);
      app.EndRequest 
    += new EventHandler(this.OnEndRequest);

      
    // 创建会话管理器
      
    //
    }


        BeginAcquireState 和 EndAcquireState 作为一个异步处理器注册到 HttpApplication._acquireRequestStateEventHandlerAsync 字段上;OnReleaseState 则负责在合适的时候清理会话状态;OnEndRequest 则是 OnReleaseState 的一个包装,负责较为复杂的请求结束处理。前面提到的 HttpApplication.InitInternal 函数,在完成了初始化工作后,会将上述这些事件处理器,加入到一个执行队列中,由应用程序在合适的时候,使用流水线机制进行调用,最大化处理效率。有关 ASP.NET 中流水线事件模型的相关介绍,请参考 HTTP PIPELINES
    Securely Implement Request Processing, Filtering, and Content Redirection with HTTP Pipelines in ASP.NET
     一文中 The Pipeline Event Model 小节,有机会我在写文章详细分析。

        知道了会话建立的调用流程再来看会话的实现就比较简单了,SessionStateModule.BeginAcquireState 被 HttpApplication 实例在合适的时候调用,处理各种会话的复杂情况后,使用 SessionStateModule.CompleteAcquireState 函数完成实际的会话建立工作,并将封装会话的 HttpSessionState 对象以 "AspSession" 为 key 加入到 HttpContext 的哈希表中,也就是前面提到的 HttpContext.Context 的由来。而 SessionStateModule.OnReleaseState 则从 HttpContext 中删除 "AspSession" 为 key 的 HttpSessionState 对象,并对会话管理器进行同步工作。

        至此,ASP.NET 中的会话建立流程大概就分析完毕了,下一小节将进一步展开分析多种不同会话管理器的实现原理与应用。
  • 相关阅读:
    [LeetCode 1029] Two City Scheduling
    POJ 2342 Anniversary party (树形DP入门)
    Nowcoder 106 C.Professional Manager(统计并查集的个数)
    2018 GDCPC 省赛总结
    CF 977 F. Consecutive Subsequence
    Uva 12325 Zombie's Treasure Chest (贪心,分类讨论)
    Poj 2337 Catenyms(有向图DFS求欧拉通路)
    POJ 1236 Network of Schools (强连通分量缩点求度数)
    POJ 1144 Network (求割点)
    POJ 3310 Caterpillar(图的度的判定)
  • 原文地址:https://www.cnblogs.com/dagon007/p/124140.html
Copyright © 2011-2022 走看看