zoukankan      html  css  js  c++  java
  • WCF把书读薄(2)——消息交换、服务实例、会话与并发

      上一篇:WCF把书读薄(1)——终结点与服务寄宿

      八、消息交换模式

      WCF服务的实现是基于消息交换的,消息交换模式一共有三种:请求回复模式、单向模式与双工模式。

      请求回复模式很好理解,比如int Add(int num1, int num2)这种方法定义就是典型的请求回复模式,请求者发送两个数字,服务回复一个结果数字。如果采用ref或者out参数,那么在xsd当中,ref参数会作为输入和输出参数,out参数只作为输出参数。在WCF当中void返回值的操作契约其实也是请求响应模式的,因为将返回值改为void,影响的只是回复消息的xsd结构,void返回的是一个空xml元素(P141)。

      对于一些调用服务记录日志等不要求有响应(即便抛异常也不需要客户端知道)的行为,应该采用单向模式,单向模式只需要在操作契约上添加单向的属性:

    [OperationContract(IsOneWay=true]
    void WriteLog(string msg);

      单向模式的操作在对应的wsdl当中没有输出节点,这样的操作必须使用void作为返回值,其参数也不能够使用ref和out参数(P144)。

      最后一类是双工模式,双工模式是在服务端定义接口,由客户端实现这个方法,服务端“回调”客户端的这个方法。这里直接扒书加法的例子,因为这个例子又简单又能说明问题,这个例子当中客户端调用服务端的加法,服务端回调客户端的显示函数。

      首先定义服务契约:

    [ServiceContract(Namespace = "http://www.artech.com/", CallbackContract = typeof(ICalculatorCallback))]
    public interface ICalculator
    {
        [OperationContract(IsOneWay = true)]
        void Add(double x, double y);
    }

    这里定义了CallbackContract属性,需要传入一个接口的名字,这个接口名字就是回调操作契约,既然在这里指明了它是个契约,就无需服务契约标签了,这里之所以采用单向,是为了防止死锁:

    public interface ICalculatorCallback
    {
        [OperationContract(IsOneWay = true)]
        void DisplayResult(double result, double x, double y);
    }

    契约实现如下:

    public class CalculatorService : ICalculator
    {
        public void Add(double x, double y)
        {
            double result = x + y;
            ICalculatorCallback callback = OperationContext.Current.GetCallbackChannel<ICalculatorCallback>();
            callback.DisplayResult(result, x, y);
        }
    }

    注意实现的第二行,先从当前操作上下文当中拿到了回调信道,之后调用它的回调方法。

    客户端实现如下:

    public class CalculatorService : ICalculator
    {
        public void Add(double x, double y)
        {
            double result = x + y;
            ICalculatorCallback callback = OperationContext.Current.GetCallbackChannel<ICalculatorCallback>();
            callback.DisplayResult(result, x, y);
        }
    }

    首先是一个回调函数的实现类,它实现了回调契约,不过老A的例子有些不雅,这里直接引了契约的dll。

    然后是客户端的主体:

    class Program
    {
        static void Main(string[] args)
        {
            InstanceContext callback = new InstanceContext(new CalculatorCallbackService());
            using (DuplexChannelFactory<ICalculator> channelFactory = new DuplexChannelFactory<ICalculator>(callback, "calculatorservice"))
            {
                ICalculator calculator = channelFactory.CreateChannel();
                calculator.Add(1, 2);
            }
            Console.Read();
        }
    }

    这里首先创建了实例上下文,用它和终结点的配置一起创建了双工信道工厂,之后通过这个工厂创建信道来实现双工调用(这里不雅同上)。

      服务端的配置如下:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <system.serviceModel>
        <behaviors>
          <serviceBehaviors>
            <behavior name="exposeExceptionDetail">
              <serviceDebug includeExceptionDetailInFaults="true"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <services>
          <service name="Artech.WcfServices.Service.CalculatorService"
                   behaviorConfiguration="exposeExceptionDetail">
            <endpoint address="http://127.0.0.1:3721/calculatorservice"
                      binding="wsDualHttpBinding"
                      contract="Artech.WcfServices.Service.Interface.ICalculator"/>
          </service>
        </services>
      </system.serviceModel>
    </configuration>

    这里采用了支持双工通信的wsDualHttpBinding绑定,客户端配置如下:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <system.serviceModel>
        <client>
          <endpoint name ="calculatorservice"
                    address="http://127.0.0.1:3721/calculatorservice"
                    binding="wsDualHttpBinding"
                    contract="Artech.WcfServices.Service.Interface.ICalculator"/>
        </client>
      </system.serviceModel>
    </configuration>

      九、实例与会话

      上面了例子里有一个InstanceContext对象, 这个对象就是实例上下文,它是对服务实例的封装,对于一个调用服务的请求,WCF会首先反射服务类型来创建服务实例,并用实例上下文对其进行封装(当然这个实例是带“缓存”的),我们可以配置一定的规则来释放上下文(P396)。

      实例上下文分为三种模式:单调模式、会话模式和单例模式。上下文的模式是服务的行为,与客户端无关,以[ServiceBehavior]的InstanceContextMode属性来设置。下面分别来看一看这三种模式。

      单调模式,表示每一次调用服务都会创建一个全新的服务实例和上下文,上下文的生命周期与服务调用本身绑定在一起(P402),这种方式能最大限度地发挥资源利用率,避免了资源的闲置和竞争,因此单调模式适合处理大量并发的客户端(P406)。

      实现单调模式需要在服务的实现类上增加反射标记:

    [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]
    public class CalculatorService : ICalculator

      从这里也能看出,服务的实现类并不代表业务逻辑,而是位于业务逻辑之上的一个“隔离层”,它显然属于服务层。

      单例模式则走了另一个极端,这种模式让整个服务器上自始至终只存在一个上下文,它的反射标签是:

    [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]

      既然只有一个上下文,那么说明同时只能处理一个请求,剩下的请求去排队或者超时。这种模式只能应付很少的客户端,而且仅限于做全局计数这样的操作。如果需要让这个服务异步执行,需要这样写反射标签:

    [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,ConcurrencyMode=ConcurrencyMode.Multiple)]

      会话模式则将为每一个服务代理生成一个上下文,会话使服务具有识别客户端的能力,所以一定要选用支持会话的绑定(P420),这种模式适合于客户端数量很少的应用。

      这种模式的服务契约上面有SessionMode标签,Required对服务的整个调用必须是一个会话,默认值为Allowed,会在适当时机采用会话模式。服务契约含有IsInitiating和IsTerminating两个属性,在客户端调用服务时,必须先调用IsInitiating为true和IsTerminating为false的,作为起始,最终要调用IsInitiating为false而IsTerminating为true的,作为终结,在两者之间可以调用全为false的操作。如果不这样调用会报错。

    [ServiceContract(SessionMode=SessionMode.Required)]
    public interface ICalculator
    {
        [OperationContract(IsInitiating=true, IsTerminating=false)]
        void Reset();
        [OperationContract(IsInitiating = false, IsTerminating = false)]
        void Add(int num);
        [OperationContract(IsInitiating = false, IsTerminating = true)]
        int GetResult();
    }

      服务实现如下,首先服务行为加上了InstanceContextMode=InstanceContextMode.PerSession,并在服务的内部保存了一个叫做result的非静态变量:

    [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
    public class CalculatorService : ICalculator
    {
        private int result;
        public void Reset()
        {
            result = 0;
        }
    
        public void Add(int num)
        {
            result += num;
        }
    
        public int GetResult()
        {
            return result;
        }
    }

      上面一共提到了InstanceContextMode和SessionMode两个枚举,当采用PerCall单调服务时,不论SessionMode如何,中间结果都不会被保存;采取Single单例服务时,不论SessionMode如何中间结果都会被保存,因为上下文是单例的;采取PerSession会话服务时,只有会话模式为Required和Allowed时,中间结果才会被保存。(P427)一张图说明问题:

      

      

      十、并发

      服务行为的InstanceContextMode表示的是对于一个请求,在服务端搞出几个实例上下文来,那么,ConcurrencyMode则表示同一个服务实例如何同时处理多个并行到来的请求,这些请求可能来自同一个服务代理的并行调用,也可能来自多个服务代理的同时调用。

      不过在使用ConcurrencyMode之前,需要先给服务/回调服务加上如下标记:

    [ServiceBehavior(UseSynchronizationContext=false)]
    
    [CallbackBehavior(UseSynchronizationContext=false)]

      这是因为服务操作会自动绑定服务的寄宿线程,为了打破这种线程的亲和性需要禁用同步上下文,否则服务就将是串行执行的,并且是采用同一个线程执行的,就没有什么“并发”可言了。(下P197)

      对于并发模式,WCF同样提供了三个可选模式。

      Single模式表示一个实例上下文在某时刻只能处理单一请求,也就是说针对某个服务上下文的并发请求会串行执行。

      在这种模式下,当并发请求到来时,WCF会对实力上下文进行上锁。

      Multiple模式表示一个实力上下文可以同时处理多个请求。

      Reentrant(可重入)模式和Single类似,只能同时处理一个请求,然而一旦这个请求处理着一半就去回调客户端了,那么在客户端响应之前,其他的并行请求还是可以被它处理的。举个不雅的例子,男人和老婆亲热着一半,老婆出去拿东西了,这时在外排队的小三就可以进来,等老婆回来了,需要先等小三出来,自己再进去……

      在这种模式下,如果需要服务端对客户端进行回调,那么要么采用OneWay的形式回调,要么就要把服务的并发模式设置为非Single,否则会造成死锁的异常,因为“小三”是会占有“原配”的锁的。(下P182)

      要让服务支持并发,需要给服务打上服务行为标签,默认值是Single,同样也可以给CallbackBehavior标签设置并发模式:

    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single)]

      同样,前面提到的实力上下文模式和并发模式也是有3*3=9种组合的。

      对于单调模式(PerCall),由于每个服务调用都使用一个实例上下文,所以根本不存在并发情况,无需设置并发模式,但是对于同一个服务代理,如果需要并行发送请求,则需要手动开启服务代理,否则服务是会串行调用的(P189)。

      对于会话模式(PerSession),并发将按照ConcurrencyMode所配置的方式进行处理。

      对于单例模式(Single),不论并发请求来自一个还是多个客户端,若ConcurrencyMode是Single则串行,是Multiple则并行,对Reentrant在回调发生时也是并行的(下P195)。

      十一、限流

       为了防止请求数量过多导致服务器资源耗尽,需要在消息接收和处理系统之间建立一道闸门来限制流量,可以通过服务器端配置给服务添加行为来进行流量控制:

    <behavior name="throttlingBehavior">
        <serviceThrottling maxConcurrentCalls="16"
                            maxConcurrentInstances="116"
                            maxConcurrentSessions="100"/>
    </behavior>

      三个属性分别为能处理的最大并发消息数量、服务实例上下文最大数量和最大并发会话数量,16、116、100分别是它们的默认值,在WCF4.0后,这些值是针对单个CPU而言的(下P204)。

  • 相关阅读:
    ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 —— 小车按键启动和蜂鸣器报警
    ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 —— 小车指定花式动作
    ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 —— 小车指定花式动作
    ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 —— 小车指定花式动作
    ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 —— 小车前后左右综合实验
    ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 —— 小车前后左右综合实验
    ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 —— 小车前后左右综合实验
    asp中设置session过期时间方法总结
    asp中设置session过期时间方法总结
    ASP.NET关于Session_End触发与否的问题
  • 原文地址:https://www.cnblogs.com/Hlia/p/3133096.html
Copyright © 2011-2022 走看看