zoukankan      html  css  js  c++  java
  • [WCF]Instance Management

    [有兴趣阅读本文的请从头至尾阅读,有兴趣帮助我解答疑问的请从尾至头读(红色部分),万分感谢!]
    我们很容易理解在旧有编程模型中关于类实例的内容。设计模式中Singleton也就是在描述着档子事。但基于WCF并非适合于以上场景,Service与Client之间要保持良好的Instance模型则需要依靠很多其他机制。

    Programming WCF Service Chapter4 对此进行了细致的描述。(更多细节请自行阅读~)

    WCF支持三种类型的Instance管理:

    InstanceManagement

    1、pre-call services:每个客户端请求对应一个instance

    2、Sessionful services:每个客户端连接对应一个instance

    3、Singleton services:所有客户端共享一个instance

    利用Behaviors可以解决这方面的问题(还有一些其他基于“服务端”的其他方面的问题可以通过使用behaviors来解决)。

    注:客户端是不知道服务端设置了什么样的behaviors的。

    VS2008MSDN:ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/fxref_system.servicemodel/html/88efb135-d425-e5b1-57d6-01a67158c1a5.htm

    Apply the ServiceBehaviorAttribute attribute to a service implementation to specify service-wide execution behavior. (To specify execution behavior at the method level, use the OperationBehaviorAttribute attribute.) This attribute can be applied only to service implementations.

    ServiceBehaviorAttribute:仅应用于服务实现。

    OperationBehaviorAttribute:用于方法级别。

    //瞧这里什么属性都没有 
    public interface IMyContract
    {} 
    //而是设置在了具体服务实现上 
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
    public class MyContract : IMyContract,IDisposable 
    {}


    设置instance模式类型由ServiceBehaviorAttribute的属性InstanceContextMode进行设置,默认值为PerSession.

    Per-Call Services

    只有当客户端调用的时候才有instance。

    为了说明问题,书中用了很形象的例子。

    Code:

    public interface IMyContract
    {
        [OperationContract]
        
    string GetData(int value); 
        [OperationContract]
        CompositeType GetDataUsingDataContract(CompositeType composite); 
        
    // TODO: Add your service operations here
        [OperationContract]
        
    void Count();
    }
    [ServiceBehavior(InstanceContextMode 
    = InstanceContextMode.PerCall)]
    public class MyContract : IMyContract,IDisposable
    {
        
    //Other Members
        public MyContract()
        {
            Trace.WriteLine(
    "WcfServiceLibrary1.MyContract()");
        } 
        
    #region IMyContract Members
        
    int count = 0;
        
    public void Count()
        {
            count
    ++;
            Trace.WriteLine(
    "Counter = " + count);
        } 
        
    #endregion 
        
    #region IDisposable Members 
        
    public void Dispose()
        {
            Trace.WriteLine(
    "WcfServiceLibrary1.Dispose()");
        } 
        
    #endregion
    }
    //Tester

    ServiceReference1.MyContractClient proxy 
    = new ConsoleApplication1.ServiceReference1.MyContractClient(); 

    proxy.Count();
    proxy.Count();
    proxy.Count();

    proxy.Close();
    Console.ReadKey();

    结果为:

    WcfServiceLibrary1.MyContract()
    Counter = 1
    WcfServiceLibrary1.Dispose()
    WcfServiceLibrary1.MyContract()
    Counter = 1
    WcfServiceLibrary1.Dispose()
    WcfServiceLibrary1.MyContract()
    Counter = 1
    WcfServiceLibrary1.Dispose()

    很明显,每次的值都是0+1的结果,这正说明了percall的方式是每个请求一个Instance的。

    Per-Session Services

    修改上面的例子:

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]

    为:

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]

    结果为:

    WcfServiceLibrary1.MyContract()
    Counter = 1
    Counter = 2
    Counter = 3
    WcfServiceLibrary1.Dispose()

    很明显Instance只有一个了。

    我们WcfServiceLibrary默认的Bind是wsHttpBinding,但若是basicHttpBinding,由于每个http到达服务端都是一个新的连接,因此服务端无法判断是哪个连接。

    增加服务端app.config中Endpoint。

    <endpoint address="basic" binding="basicHttpBinding" name="basic" contract="WcfServiceLibrary1.IMyContract" />

    重新导入后修改Program里的程序:

    ServiceReference1.MyContractClient proxy = new ConsoleApplication1.ServiceReference1.MyContractClient("basic");

    ServiceReference1.MyContractClient proxy = new ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");

    其中basic和WSHttpBinding_IMyContract为两种不同形式的服务在客户端的Endpoint.Name。

    之前默认WSHttpBinding_IMyContract,现在由于存在多个Endpoint,则需要显示指定。

    现指定为basic。再次运行,结果:

    WcfServiceLibrary1.MyContract()
    Counter = 1
    WcfServiceLibrary1.Dispose()
    WcfServiceLibrary1.MyContract()
    Counter = 1
    WcfServiceLibrary1.Dispose()
    WcfServiceLibrary1.MyContract()
    Counter = 1
    WcfServiceLibrary1.Dispose()

    其结果与PerCall是相同的。

    通过SessionId可以获得Instance的SessionId

    使用Per-Session方式可以通过设置SessionMode属性(允许、必须、不允许三种枚举)。

    SessionMode:Gets or sets a value that indicates whether a session is required by the contract.

    SessionMode 枚举http://msdn2.microsoft.com/zh-cn/library/system.servicemodel.sessionmode.aspx

    Allowed(允许)
    Specifies that the contract supports sessions if the incoming binding supports them.

    如果绑定支持Session的话,则让其支持,否则按照可以支持的方式,比如PerCall的方式进行支持。

    Required(必须)
    Specifies that the contract requires a sessionful binding. An exception is thrown if the binding is not configured to support session.

    指定契约必须使用Sessionful的方式。如果不支持,则抛出异常。

    NotAllowed(不允许)
    Specifies that the contract never supports bindings that initiate sessions.

    指定不能使用Sessionful的方式。作者推荐是用NotAllowed的时候仅用PerCall方式。

    刚才由于我添加了basic的方式,因为默认选中Allowed,因此刚才的之所以结果与PerCall相同,是因为它,下面我将其修改为Required。

    [ServiceContract]

    修改为

    [ServiceContract(SessionMode=SessionMode.Required)]

    结果为一个运行时错误:

    System.InvalidOperationException: Contract requires Session, but Binding 'BasicHttpBinding' doesn't support it or isn't configured properly to support it.
       at System.ServiceModel.Description.DispatcherBuilder.BuildChannelListener(StuffPerListenUriInfo stuff, ServiceHostBase serviceHost, Uri listenUri, ListenUriMode listenUriMode, Boolean supportContextSession, IChannelListener& result)
       at System.ServiceModel.Description.DispatcherBuilder.InitializeServiceHost(ServiceDescription description, ServiceHostBase serviceHost)
       at System.ServiceModel.ServiceHostBase.InitializeRuntime()
       at System.ServiceModel.ServiceHostBase.OnOpen(TimeSpan timeout)
       at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
       at System.ServiceModel.Channels.CommunicationObject.Open()
       at Microsoft.Tools.SvcHost.ServiceHostHelper.OpenService(ServiceInfo info)

    但是若使用wsHttpBinding,但却without security and without reliable messaging也将无法维持transport-level session。

    添加如下代码到app.config(Service)(指定其为无安全并且不可靠消息)

    <system.serviceModel><!-- Other service Model -->

    <bindings>
        
    <wsHttpBinding>
          
    <binding name="wsBinding">
            
    <reliableSession enabled="false" />
            
    <security mode="None" />
          
    </binding>
        
    </wsHttpBinding>
      
    </bindings>
    </system.serviceModel>

     

    <endpoint address="" binding="wsHttpBinding" contract="WcfServiceLibrary1.IMyContract" bindingConfiguration="wsBinding" >


    增加绑定配置为wsBinding

    确保服务端程序为:
    [ServiceContract(SessionMode = SessionMode.Allowed)]
    (或者没设)
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]

    运行结果与PerCall的结果相同。其原因也就是因为wsHttpBinding未设置安全可靠的Session。

    超时

    inactivityTimeout:超时时间

    在连接空闲的情况下,以客户端和服务端的超时时间中最短的那个来决定是否移除Instance,若之后再调用则会抛出异常。

    作者额外注明可以采用:AutomaticSessionShutdown属性。其设置为true则当proxy.Close()的时候自动关闭Session,设为false的时候则只有在服务端将服务关闭才会关闭Session。

    但是,若将其修改为NotAllowed

    则结果与PerCall相同(手动写为PerSession)。(不管服务配置如何,它总会是PerCall。因为TCP和IPC协议总是维持transport level,你不能将它们配置SessionMode.NotAllowed,它们会在服务载入时进行验证。作者建议是“在选择使用SessionMode.NotAllowed的同时,将服务配置为PerCall”。)

    Singleton Service

    Singleton,顾名思义就是仅有一个Instance,供所有客户端调用。

    在说明问题之前先修改上面的例子:

    //[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    //[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
    为:
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]

    Tester中:

    static void Main(string[] args)
    {
        ServiceReference1.MyContractClient proxy 
    = new ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");
        proxy.Count();
        proxy.Count();
        proxy.Count();
        Console.WriteLine(proxy.Endpoint.Name);
        Console.WriteLine(proxy.InnerChannel.SessionId);
        proxy.Close();
        Console.WriteLine(proxy.Endpoint.Name);
        Console.WriteLine(proxy.InnerChannel.SessionId);
        Console.WriteLine(
    "_________________________________________________________");
        Console.ReadKey(); 
        ServiceReference1.MyContractClient proxy1 
    = new ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract"); 
        proxy1.Count();
        proxy1.Count();
        proxy1.Count();
        Console.WriteLine(proxy1.Endpoint.Name);
        Console.WriteLine(proxy1.InnerChannel.SessionId);
        proxy1.Close();
        Console.WriteLine(proxy1.Endpoint.Name);
        Console.WriteLine(proxy1.InnerChannel.SessionId);
        Console.WriteLine(
    "_________________________________________________________");
        Console.ReadKey(); 
        ServiceReference1.MyContractClient proxy2 
    = new ConsoleApplication1.ServiceReference1.MyContractClient("basic"); 
        proxy2.Count();
        proxy2.Count();
        proxy2.Count();
        Console.WriteLine(proxy2.Endpoint.Name);
        Console.WriteLine(proxy2.InnerChannel.SessionId);
        proxy2.Close();
        Console.WriteLine(proxy2.Endpoint.Name);
        Console.WriteLine(proxy2.InnerChannel.SessionId);
        Console.WriteLine(
    "_________________________________________________________");
        Console.ReadKey();
    }


    运行的结果:(Output<Debug>)

    //客户端调用前

    //...
    //其他代码
    //...
    WcfServiceLibrary1.MyContract()
    //...
    //其他代码
    //...

    //客户端调用后

    //...
    //其他代码
    //...
    Counter = 1
    Counter = 2
    Counter = 3
    Counter = 4
    Counter = 5
    Counter = 6
    Counter = 7
    Counter = 8
    Counter = 9
    //...
    //其他代码
    //...

    从Counter的值看来,多个proxy调用的是同一个Instance。

    值得一提的是WcfServiceLibrary1.MyContract(),也就是构造函数的调用时间是在Service启动的时候,而PerCall与Sessionful构造函数调用时间都是在proxy调用之时。而且只有当Host关闭的时候才会Dispose()。

    MSDN:

    If the InstanceContextMode value is set to Single the result is that your service can only process one message at a time unless you also set the ConcurrencyMode value to Multiple.

    也就是说除非将服务设置成多线程的,否则在一个时间只能处理一个消息请求。

    从HOST端控制SingletonInstance

    方式一:

    修改代码:

    static void Main(string[] args)
    {
        Uri baseAddress 
    = new Uri(http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary1/MyContract/);
        WcfServiceLibrary1.MyContract singleInstance = new WcfServiceLibrary1.MyContract();
        singleInstance.CountProperty 
    = 200;//设置初始值200
        System.ServiceModel.ServiceHost host = new System.ServiceModel.ServiceHost(singleInstance);
        host.AddServiceEndpoint(
    typeof(WcfServiceLibrary1.IMyContract), new WSHttpBinding(), baseAddress);
        host.Open();
        Console.WriteLine(
    "host state = {0}", host.State.ToString()); 
        ServiceReference1.MyContractClient proxy 
    = new ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");
        proxy.Count();
        proxy.Count();
        singleInstance.CountProperty 
    = 100;//修改singletonInstance.CountProperty为100
        proxy.Count();
        Console.WriteLine(proxy.Endpoint.Name);
        Console.WriteLine(proxy.InnerChannel.SessionId);
        proxy.Close();
        Console.WriteLine(proxy.Endpoint.Name);
        Console.WriteLine(proxy.InnerChannel.SessionId);
        Console.WriteLine(
    "_________________________________________________________");
        Console.ReadKey(); 
        host.Close();
    }

    public class MyContract
    添加属性:
    public int CountProperty
    {
        
    set
        {
            count 
    = value;
        }
    }

    无须启动WcfServiceLibrary1(将用外部Host进行启动)直接运行客户端程序:

    结果:

    //客户端调用前

    //...
    //其他代码
    //...
    WcfServiceLibrary1.MyContract()
    //...
    //其他代码
    //...

    //客户端调用后

    //...
    //其他代码
    //...
    Counter = 201
    Counter = 202
    Counter = 101
    //...
    //其他代码
    //...

    方式二:

    通过OperationContext.Current.Host 来获取当前进程的host

    此方法我暂时未调出来,大家有想到或做到的麻烦告诉我!

     

    ServiceHost host = OperationContext.Current.Host as ServiceHost;
    MyContract singletonInstance 
    = host.SingletonInstance as MyContract;
    if (singletonInstance != null)
        singletonInstance.CountProperty 
    = DateTime.Now.Second; 

     

    先假设以上方法可行吧。

    现在我遇到的问题:

    1、OperationContext.Current这里的(MSDN:Gets or sets the execution context for the current thread.
    )current thread是指我ConsoleApplication也就是Client的线程呢,还是指Service端的线程?(据我常理分析应该是服务端的线程)。理论上我应该在Count()方法内写这段代码,但是问题又涉及到通过Client端进行调用时是使用proxy,这样真正的情况会是怎样呢?

    2、因此我又写了一个在一个ConsoleApplication里完成服务的代码,此时在host.SingletonInstance的确是被赋值了(不再是null了),但是紧接着我调用OperationContext.Current却发现其为null,也就是说这里的OperationContext并没有被赋值。继而将其转移到同在一个程序内的MyContract.Count()方法中,但是其值仍然为空,因此此法再度失效。

    希望作为高手的您能够提供一个使用OperationContext.Current的场景。什么样算是OperationContext的当前线程?

     

    推荐阅读:

    http://www.microsoft.com/china/MSDN/library/Windev/WindowsVista/WCFEssentials.mspx?mfr=true

  • 相关阅读:
    一、初识数据库
    面向对象—基础、名称空间、三大特性
    六、内置函数
    五、迭代器、生成器、装饰器
    四、global和nonlocal、函数名应用、格式化输出
    三、名称空间
    二、函数的参数
    shell脚本
    线性代数
    [模块] python调用java代码-jpype
  • 原文地址:https://www.cnblogs.com/volnet/p/instance_management.html
Copyright © 2011-2022 走看看