橙树的Blog

导航

WEB SERVICE中的状态管理

引用: http://developer.ccidnet.com/art/322/20021202/32324_1.html

vs.net2005上调试通过,修正了一些bug

WEB SERVICE大概是.NET中最引人瞩目的一个技术亮点了。事实上Microsoft在不遗余力的推动WEB SERVICE,力图使之成为新一代的分布式计算模式的标准。WEB SERVICEMicrosoftCOM/DCOM之后的最重要的技术革新。与以往不同的是,Microsoft这次的革新是建立在业界开放的标准之 上的。SOAPHTTP WSDL以及UDDI并非Microsoft所有,所以在WEB SERVICE上的技术投入并不会将你Microsoft身上。SUN IBM以及ORACLE都提供自己的WEB SERVICE方案,所以你有选择,可以使用非Microsoft的其它方案,至少在理论上是这样。

简言之,WEB SERVICE其实就是在Web之上提供服务(SERVICE〕。客户端可以调用远程服务器端函数并得到结果,就像RMIDCOMCOBRA一样。不 同的是客户端和服务器端的对话使用的是业界标准而不是JAVAMicrosoft独有的技术。由于采用了HTTP作缺省通讯协议,使得WEB SERVICE可以透过各个企业,公司的防火墙,真正实现跨INTERNET的分布式计算。也因为HTTP,使得WEB SERVICE在本质上一些先天的限制,就像其它的WEB应用程序一样。HTTP是一种无态的(STATELESS)通讯协议,所以在HTTP之上的 WEB SERVICE如何保持状态(STATE就成为一个有趣的话题。在这里,我想就这个问题做一些讨论。

从一个简单的例子看WEB SERVICE无态性

Microsoft提供的VISUAL STUDIO.NET中,集成的开发环境将很多WEB SERVICE的繁琐细节隐藏了起来,尽量的想使开发者感到方便和简单,就像在开发普通的类(CLASS)和函数那样。但是你千万不能被表象所蒙蔽,从而 导致一些最基本的错误。请看下面这个小例子。

class Student : System.Web.Services.WebService

{

    private String name;

    public String Name

    {

        get{return name;}

        set{name=value;}

    }   

}

一眼看上去,这个Student类没有什么毛病。另外,你在客户端想这样的使用它。

...

localhost.Student student = new localhost.Student();

student.Name = "Lao Wang";

String myInfo = student.Name();

...

如果这不是一个WEB SERVICE,那么客户端和服务器端都没有问题。但是在WEB SERVICE里,属性(PROPERTY)是不能成为公共接口的一部分的(PUBLIC INTERFACE),所以getset是行不通的。

OK,你可能会这样修改一下这段小程序

class Student : System.Web.Services.WebService

{

    private String name;

    [WebMethod]

    public String getName()

    {

        return name;

    }

    [WebMethod]

    public void  setName(String aName)

    {

        name = aName;       

    }   

}

客户端的程序为

...

localhost.Student student = new localhost.Student();

student.setName("Lao Wang");

String myInfo = student.getName();

...

这下应该没有问题了吧。但当你运行客户端程序的时候,你发现结果和你预料的不一样。getName函数返回值是NULL。奇怪,刚刚设置的"Lao Wang"哪儿去了呢?

如果你遇到这样的问题,那说明你被集成环境给蒙蔽了,没有知道程序究竟是怎么执行 的。看起来你只创建了一个Student实例(Object),并两次调用它的函数。但实际情况却非如此。你没有创建一个Student实例,你只是创建 了Student类的代理类的实例(Proxy Class)。Student类的实例是在服务器端创建的,而且是两次。你在客户端两次的函数调用在服务器端生成了两个实例,每个实例响应一次函数调用。 两个实例之间没有任何联系,所以你把第一个实例的Name设为"Lao Wang"对于第二个实例来讲是全然无知的。现在,你可能体会到"无态"的涵义了吧。

如果你知道一些WEB SERVICE的背景REMOTING的话,这一点就更好理解了。WEB SERVICE的实例创建是属于REMOTING"单一调用"方式的(SINGLE CALL)。意思是每一次的函数调用都会在服务器端创建一个新的实例。(REMOTING还支持SingletonClientActivation两 种方法)

那么解决如何保存"状态"呢?就前面的这个小问题来说,你可以这样修改一下程序。

class Student : System.Web.Services.WebService

{

    private String name;

    [WebMethod (EnableSession=true)]

    public String getName()

    {

        return (String)Session["NAME"];

    }

    [WebMethod (EnableSession=true)]

    public void setName(String aName)

    {

        Session["NAME"] = aName;

    }   

}

客户端的程序为

...

localhost.Student student = new localhost.Student();

student.CookieContainer = new System.Net.CookieContainer();

student.setName("Lao Wang");

String myInfo = student.getName();

...

这样一改,问题就解决了。其"奥秘"在于你引入了ASP.NET中的"会话状态"管 理机制(SESSION STATE MANAGEMENT)。如果你做过ASPASP.NET开发的话,你就回马上明白这一点。就象我前面所说, WEB SERVICE是在WEB上提供的SERVICEWEB上的许多东西在WEB SERVICE里同样适用。"会话状态"管理机制是WEB SERVICE中最普遍采用的一种保存"状态"的方法,它将信息存在于服务器端,所以适宜保存比较敏感的信息,例如信用卡号码,银行帐号什么的。并且 SESSION中可以存放各种复杂的数据结构甚至是COM对象的实例,这使得它可以用来实现很复杂的状态保存。

SESSION的一些基本常识

1. Session Cookie

ASP.NET中,会话(Session)是用一个120位的字符串 (String)来表示的。ASP.NET负责生成和管理这个变量。它被存放在一个特殊的Session Cookie里(ASP.NET_SessionId)。在ASP.NET里,这个变量可以嵌在URL里,但在WEB SERVICE里这个选项不被支持。在WEB SERVICE里使用Cookie使得它必须和HTTP一起使用。如果你想用SMTP协议来和WEB SERVICE通讯,那么Cookie将不再能使用。好在在多数情况下,这并不是一个问题。

2. 如何使用"会话状态"

象前面的例子那样,设置WebMethodEnableSession属性为 "True"就可以了。"会话状态"的使用是基于函数的而不是基于类的。这样设计的原因是为了提高性能。如果一个函数使用了EnableSession属 性,即使函数体里没有使用任何状态变量,ASP.NET还是要做对于状态进行例行检查的,这是不必要的浪费。所以Web Method的缺省状态是EnableSessionFalse。值的注意的是EnableSessionFalse只表示这个函数不使用"会话状态 ",并不表示没有"会话状态"。因为设置它为TrueFalse并不回生成或销毁"会话状态",只表示这个函数是否使用"会话状态"

如果你定义的类是System.Web.Services.WebService 的子类,那么你可以直接使用"会话对象"Session Object),就像前面的例程那样。或者你可以通过System.Web.HttpContext.Current.Session来得到"会话对象 "

Session mySession = System.Web.HttpContext.Current.Session;

String name = mySession["NAME"];

在客户端,程序里要有相应的调整。客户端的代理类从 System.Web.Services.Protocols.SoapHttpClientProtocol中继承了许多有用的属性,其中 CookieContainer就是一个。它的作用是存放每一个WebMethod请求中发送和接收的Cookies。如果在客户端想使用"会话",那么 只要创建一个CookieContainer的实例就可以了。

localhost.Student student = new localhost.Student();

student.CookieContainer = new System.Net.CookieContainer();

在有些时候,在释放了代理对象的的引用后(Proxy Object Reference〕,你可能还想再使用"会话状态"中的变量。这种情况下,你需要把CookieContainer里的Cookie或是部分Cookie拿出来存在什么地方。

localhost.Student student = new localhost.Student();

//创建一个System Uri对象

Uri uri = new Uri("http://yourServer")

//从代理对象中获取Cookies

System.Net.CookieCollection cookies = student.CookieContainer().GetCookies(uri);

当你拿到cookies对象后,你可将它存放在什么地方以后使用,或是传给其它使用同一WEB SERVICE类或对象,只要是"会话"还没有过期(Expire)。

3."会话状态"的四种配置方式。

"会话状态"可以通过修改machine.config文件以及 Web.config文件来设置。machine.config文件是用来配置整个计算机的,而Web.config是针对具体每一个WEB SERVICE的。通常我们是通过设置这个文件来改变"会话状态"模式的。下面给出的是一个比较典型的web.config文件的有关片段。

<?xml version="1.0" encoding="utf-8" ?>

<configuration>   

  <system.Web>

       ...

    <!--  SESSION STATE SETTINGS

          By default ASP .NET uses cookies to identify which requests belong to a

particular session.

          If cookies are not available, a session can be tracked by adding a

session identifier to the URL.

          To disable cookies, set sessionState cookieless="true".

    -->

    <sessionState

            mode="InProc"

            stateConnectionString="tcpip=127.0.0.1:42424"

            sqlConnectionString="data source=127.0.0.1;user id=sa;password="

            cookieless="false"

            timeout="20"

    />

    ...

 </system.Web>

</configuration>

ASP.NET中,mode4个可选值。

a. Off -- 禁止WEB SERVICEWebMethod使用会话状态。

b. InProc -- 缺省设置。会话的状态存放在IIS的进程里,这种设置有最快的存取速度,但可扩展性能不好。别的进程以及其它计算机的进程无法存取InProc里的会话状态变量。对于开发人员在开发调试时,这是最常用的选择。

c. StateServer -- 使用专用的进程来管理"会话状态"。这是ASP.NET引入的一个新技术。.NET提供了一个后台进程来专门管理会话状态,这使得会话状态不再局限在一台 计算机上,多台提供Web服务的计算机可以共用一台计算机的一个后台进程来专门管理会话状态,使得会话状态变量在WEB FARM中的使用成为可能,从而极大的提升了程序的可扩充性能。但是StateServer的会话状态变量存取要跨越进程甚至是网络,其速度比 InProc要慢许多,而且多台Web服务器共享一个后台进程来管理"会话状态"会有潜在的可靠性问题。一旦这个后台进程出现问题,那么多台Web服务器 将同时丢掉所有的状态信息。用术语来说,StateServer 是一个"Signle Point Failure"。在理论上讲,这不是一种理想的方案。

另外如果多台Web服务器共享一个StateServer,那么你可能还要修改 machine.config里的machine key(用来加密会话状态数据的)。多台Web服务器要使用同样的设置。validationKeydecryptionKey是一个40-128位长 的16进制的字符串。例如

<machineKey 

validationKey="123456789adcdef123456789adcdef123456789adcdef123456789adcdef"

decryptionKey="abcd123456789adcdefabcd123456789adcdefaaa123456789adcdef000"

          validation=SHA1"

bordercolorlight=black bordercolordark="#FFFFFF" v:shapes="_x0000_i1025">

(图为管理ASP.NET会话状态的后台进程,C:\WINNT\Microsoft.NET\Framework\v1.0.3705\aspnet_state.exe

d. SqlServer -- 使用SQL数据库来管理会话状态变量。其想法和StateServer一样,是想让多台Web服务器可以共享会话状态变量(存放在SQL中的tempdb 数据库中),使用数据库可能会比后单一台进程更稳定,扩充性能更好,但它的存取速度也是最慢的。是否使用它要根据你的具体需要来确定。

Timeout的设置很好理解。如果Timeout20表示如果20分钟没有收 到请求,会话状态将会被清理。如果你想做进一步的努力,你可以提供一个WebMethod,客户端程序可以通过调用它来立即释放会话状态变量。当然,如果 客户端程序不调用它,你也没有办法强迫。

[WebMethod (EnableSession=true)]

public String ReleaseSession()

{

    Session.Abandon();

}

客户端的程序相应为

...

localhost.Student student = new localhost.Student();

student.CookieContainer = new System.Net.CookieContainer();

student.setName("Lao Wang");

String myInfo = student.getName();

student.ReleaseSession();

...

无态WEB SERVICE的设计模式

虽然ASP.NET提供了很多简便易用的方法来方便状态管理,但在理论上讲,"有态" WEB SERVICE总是要加重服务器端的负荷,存在可扩展性的问题。那么能否把状态信息存放在客户端,由客户端程序维护呢?这种想法导致了另外一种设计思路, 即"无态"WEB SERVICE。在具体实现过程中,通常有以下这样几种作法。

1. Custom Cookies

作过WEB开发的程序员都知道,客户端的Cookie可以用来记录状态信息。在WEB SERVICE里客户端的Cookie同样适用。具体来说,可以把如何使用客户端用户自定义的Cookie归纳为以下4步。

a. WEB SERVICEURL创建Cookie对象。

b. Cookie对象插入信息。

c. Cookie对象拼接到Resonse对象中。这样Cookie将会传到客户端。以后客户端发出的请求将会夹带这个Cookie

d. WebMethod从客户端的请求中提取Cookie信息,并做相应处理。

class Student : System.Web.Services.WebService

{

    private String name;

    [WebMethod (EnableSession=true)]

    public String getName()

    {

        HttpCookie cookie = Context.Request.Cookies["USER_INFO"];

        return cookie.Values["NAME"];

    }

    [WebMethod (EnableSession=true)]

    public void setName(String aName)

    {

        HttpCookie cookie = new HttpCookie("USER_INFO");

        cookie.Values.Add("NAME", aName);

        Context.Response.AppendCookie(cookie);       

    }   

}

从上面的这个小例程来看,使用Custom Cookies同样可以保存"状态"信息。但需要指出的是客户端用户自定义的Cookie有许多具体的局限。

a. 只能和HTTP协议一同使用。

b. 只能保存字符串信息,不够安全并且信息长度也有限制。

c. 用户无法控制Custom Cookies何时被送出。

基于这些特点,一般不建议使用这种技术来保存重要的信息。Custom Cookies适宜用来传送一些小量的辅助性信息,在服务器端,如果这些信息没能拿到,应不至于影响函数的正常执行。

2. SOAP Headers

SOAP消息有三个部分:信封(Envelope),信头(Headers〕和信体(Body)。通常我们只使用信封和信体,信头是没有被使用的。使用信头可以存放客户端的状态信息。在程序中使用SOAP信头要做以下几步。

a. 定义SOAP Header的类

public class UserHeader : System.Web.Services.Protocols.SoapHeader

{

    public String userName;

}

b. 改动服务器端的程序

class Student : System.Web.Services.WebService

{

// 必须定义为public, 否则不能作为 soapheader()的参数

    Public UserHeader userHeader;

    [WebMethod, Soapheader("userHeader")]

    public String getName()

    {

        return userHeader.userName;

    }

}

c. 更改客户端的程序

...

localhost.UserHeader header = new localhost.UserHeader();

header.userName = "Lao Wang";

//生成SimpleHeaderService实例

localhost.SimpleHeaderService service = new localhost.SimpleHeaderService()

service.UserHeaderValue = header;

String myInfo = student.getName();

student.ReleaseSession();

...

当你使用SOAP Header后,代理类中会自动生成一些程序代码,使你可以方便的在客户端使用信头。

当你正式的使用这种技术的时候,你回发现事实上的情况可能比这个小例程要复杂。之所 以不想在这里展开叙述它是因为它现在还有些兼容性的问题没有解决。SUNMicrosoft以及IBM对信头的实现不尽相同,有的软件产品可能还不支持 这一技术,所以在选用它的时候要考虑兼容性的问题。另外这一技术局限于只能用SOAP的方法来访问WebMethodHTTP GETHTTP POST都不支持SOAP信头。这也许会成为影响你决定的一个因素。

事实上。你可以在服务器端使用HTTPApplication以及Cache来存放信息,就像在ASP.NET应用程序里那样,但严格的讲它们存放的是进程内的公共信息,而不是使用进程的某一个客户的私有信息,和本文讨论的话题略有距离,所以不再赘述了。

posted on 2006-08-02 15:41  橙树的Blog  阅读(625)  评论(1)    收藏  举报