Web服务中对象状态的持续化

在Web服务中,每个请求通常被视为一个全新的请求。这意味着,如果想跟踪一系列相关的调用,必须建立一种方式来识别当前的请求是否是一个新请求,或者是否与另一个请求相关。一种方法是为第一个请求生成一个令牌(token),然后为相关的请求重用这个相同的令牌。这个令牌可以是一个数字,也可以是一个复杂的对象,唯一重要的是这个令牌是保证唯一的。如果每个请求都发送这个令牌,那么一切都会正常工作,因为可以确定当前请求属于哪个组。也可以从另一个方向来实现:客户端用一个唯一的用户名作为令牌进行调用,基于那个令牌进行通信。

然而,另一个问题出现了,这个问题更加重要。如果所有的请求都与同一个对象相关呢?如果需要在同一对象上多次调用方法,并且该方法修改了对象呢?需要一种方式在调用之间存储对象,以便可以用每个新调用来修改对象。这就是接下来要讨论的。使用令牌概念,将描述一种在对同一对象的连续调用之间维护对象状态的方法。

实现方式

实现这种功能有几种方式。本文只讨论其中的两种。这两种方式的共同点在于理念,不同之处在于实现细节。

假设使用客户端使用用户名作为令牌,并请求Web服务的方法。当请求被发起时,Web服务从请求的参数中检索令牌,并将其与列表进行比较。如果列表中没有找到令牌,那么这是该令牌的第一个请求,为该请求创建一个新对象。然后将令牌添加到令牌列表中。如果列表中找到了令牌,那么这是一个连续的请求,对象是可用的。然后检索对象。它将包含当前数据(最后一次请求修改的数据)。

现在有了对象(新实例化或检索到的),现在可以使用对象并调用需要的方法。使用完对象后,是时候将其存储起来,以便再次需要时可以使用。

Application对象的使用

ASP.NET Web应用程序(包括Web服务)有一个名为Application State的设施。通过Application属性访问这个设施,可以在请求之间存储不同的对象。这样存储的对象对整个应用程序都是可用的。

关于会话状态和存储方式的讨论会很长。实际上,可以使用状态服务器,甚至SQL Server来保存状态数据。如果正在开发大型应用程序,这是应该走的路。然而,对于小型应用程序,这些解决方案大多数时候是不必要的。

希望已经清楚了Application对象是所需要的。对象可以像在哈希表中一样存储在Application对象中。可以使用令牌(它是唯一的)作为键,将对象作为值存储。

这个解决方案很好;然而,它确实有一个缺点:状态不是持久的。当服务器重新启动时,状态会丢失。此外,ASP.NET工作进程可能会被Internet信息服务(IIS)回收。这意味着,有时可能会丢失Application对象中的数据。

使用这个设施进行编程非常简单,如下代码所示:

public string MethodCall_ver1(int token) { UserDefinedClass d = null; // 如果Application对象中没有找到令牌,检索它 if (Application[token.ToString()] != null) { d = (UserDefinedClass)Application[token.ToString()]; } else { // 如果没有找到令牌,那么就创建一个新对象 d = new UserDefinedClass(); } // 调用对象的方法 // 将对象保存到Application对象中 Application[token.ToString()] = d; // 服务返回 return string.Format("{0}", d.something.ToString()); }

只需要检查Application对象中是否存储了指定令牌位置的对象。如果没有,那么就实例化一个新对象。使用完它后,将其存储在Application对象中。

使用文件系统

文件系统是跨系统重启存储对象的好方法。此外,即使文件系统是分层次的,仍然有一种方法可以唯一地识别文件夹中的文件:使用文件名。所以,令牌可以用作文件名。

当请求到来时,方法将搜索一个名为令牌的文件。理念保持不变,只是实现方式发生了变化。不是在Application对象中查找,而是检查文件是否存在。然而,有一个问题。需要将内存中的对象存储到文件系统中。这意味着必须调用一个称为序列化的过程,将对象的字节存储到文件流中。相反的过程称为反序列化,包括从流中读取对象的字节并重新创建对象。

幸运的是,.NET框架允许这样做,非常简单!BinaryFormatter类就是用来做这个的。下面有代码可以做到这一点。正如看到的,唯一改变的是保存和检索对象的方式:

// 在web.config中有一个设置,告诉应该在哪里存储文件。 // 设置被称为TemporaryDirectory // 检查指定目录中的文件是否存在。 // 如果存在,这不是第一个请求,检索它 if (File.Exists(string.Format("{0}{1}.dat", System.Configuration.ConfigurationManager.AppSettings["TemporaryDirectory"], id))) { // 获取一个指向该文件的FileStream FileStream sr = new FileStream(string.Format("{0}{1}.dat", System.Configuration.ConfigurationManager.AppSettings["TemporaryDirectory"], id), FileMode.Open); // 创建一个用于序列化/反序列化对象的对象 BinaryFormatter bf = new BinaryFormatter(); // 反序列化对象 d = (UserDefinedClass)bf.Deserialize(sr); sr.Close(); } else { // 如果文件不存在,那么创建一个新对象 d = new UserDefinedClass(); } // 调用对象的方法 // 将对象反序列化回文件系统 FileStream sw = new FileStream(string.Format("{0}{1}.dat", System.Configuration.ConfigurationManager.AppSettings["TemporaryDirectory"], id), FileMode.OpenOrCreate); BinaryFormatter abf = new BinaryFormatter(); abf.Serialize(sw, d); sw.Close(); // 服务返回 return string.Format("{0}", d.something.ToString());

TemporaryDirectory存储在web.config中,文件中的条目是:

<add key="TemporaryDirectory" value="C:\Inetpub\wwwroot\WebServicePersistent\temp\" />

这就是需要做的,以便有一个Web服务,它在请求之间持续其状态。

这种解决方案的优点是,即使在系统崩溃和系统重新启动之间,状态也是持久的。这是一件好事。然而,有授权访问的问题。存储在服务器上的文件可能会被那些不太善意的人删除。这意味着,如果对象包含敏感数据,就会有一个漏洞。为了防止这种情况发生,必须在存储序列化对象的文件夹上设置适当的ACL(访问控制列表),并确保IIS不会将其提供给任何人!

测试解决方案

要做到这一点,只需要用相同的令牌反复调用Web服务,并看看something属性是否继续递增。

性能考虑

虽然在Application对象中存储对象看起来是更好的解决方案,但随着需要服务的请求数量的增加,所有的对象都存储在服务器内存中。这可能是一个巨大的问题,甚至可能导致DDoS攻击。

另一方面,虽然使用基于文件的方法,每次读写磁盘都会受到惩罚。然而,内存只有在需要时才分配,所以没有DDoS。

如果想要在同一Web服务的连续调用之间有持久的状态,可以采用介绍的解决方案之一。要么使用Application对象存储对象,要么将对象序列化并存储在磁盘上的文件中。

Application对象的方法更适合于频繁访问的小对象,而文件方法更适合于不频繁访问的大对象。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485