代码改变世界

WCF(五)拾遗

2017-03-21 16:41  sunice  阅读(22)  评论(0编辑  收藏  举报

1、WCF服务端和客户端交换数据采用元数据交换的方式。元数据交换的核心是wsdl文档。交换过程是客户端向服务端发起交换元数据的请求-->服务端运行时将元数据编写成wsdl文件-->客户端获得wsdl文件-->客户端翻译文件-->客户端根据翻译结果生成本地类代码和配置-->客户端获得服务的本地编程模型

2、服务端公开元数据需要配置,不同的配置会导致元数据公开的方式不同

WCF服务端公开元数据必须具备两个条件:

(1) 为服务添加ServiceMetadata行为。

(2) 打开元数据交换终结点。

二者缺一不可。

3、WCF的公开元数据的手段主要有两种:

第一种:通过HTTP GET方法。

如果想采用这种元数据公开方式,必须配置服务的ServiceMetadata行为,并指定httpGetEnabled = "true",而元数据公开终结点不必配置,系统会自动配置。

第二种:通过MEX元数据交换终结点。

在这种方式下,我们首先要保证服务拥有ServiceMetadata行为,但是httpGetEnabled可以不必为true。此外我们还需要为服务显式地添加一个终结点,这个终结点的地址、绑定和协定都是指定的我们不能更改。

使用svcutil.exe或服务引用的时候可以不关心元数据公开方式是HTTP GET还是Mex,他们会自动寻找到合适的方式,只需要把服务的svc文件地址输入就可以了,但是我们应该知道,这两种元数据公开的方式是有区别的。

----------WCF会话----------------

1、WCF会话。有些逻辑需要客户端和服务端通信多次才能完成,在这个期间双方需要记住彼此,而且会话也是很多其他特性实现的基础,例如双工通信。

2、如何建立会话

     (1) 需要支持会话的绑定。绑定描述了双方的通信方式,不同的绑定对会话的支持是不同的,比如basicHttpBinding是不支持会话的,而wsHttpBinding就是支持的。要建立会话通信,这个通信必须首先使用支持会话的绑定。

     (2)要用到在修饰服务协定的SeviceContract属性,我们知道被这个属性修饰的接口是一个服务协定,其实这个属性也拥有属性,其中一个属性叫做SessionMode。这是一个枚举,我们通过设置这个枚举的值来配置服务协定是否支持会话。

[ServiceContract(SessionMode = SessionMode.Required)]
    public interface IHelloWCF
    {
        [OperationContract]
        string HelloWCF();
    }

  

这段代码中,我把SessionMode设置为了Required,这表示调用这个服务协定的客户端必须使用会话。

SessionMode有三个可能的值:

1) Allowed:这是默认值,表示这个服务协定是允许会话的,客户端可以选择用会话连接,也可以选择不用会话连接。

2) Required:表示服务协定要求客户端连接必须使用会话。

3) NotAllowed:表示服务协定不允许使用会话连接。

 

这些配置需要搭配其他的配置才能起到实际意义,比如服务实例模式,服务端和客户端调用模式等等。

----------WCF实例化------------

1、实例化。那么,什么是实例化呢?我们知道,要调用一个类的方法,如果这个类不是静态类,首先要将这个类实例化从而获得这个类的一个对象,然后在这个对象上调用方法,这是面向对象的基本知识,而我们服务端上寄存的服务,也是一个类,是一个实现了服务接口的类,因此,客户端在调用这个类的方法时,服务端一定也要将这个类先实例化出一个对象,然后在这个对象上调用方法,将结果返回给客户端。

2、实例化的几种模式

按照最简单的考量,每次客户端的方法调用服务端都实例化一个对象然后为其服务,调用完就把对象销毁,也就是随用随new。这样看上去最简单明了,但是性能可能不太好,创建对象和销毁对象都是有开销的。如果客户端和服务端使用会话来连接,那么服务端就可以实例化一次,然后在整个会话期间都使用这一个对象为其服务,这样开销就小一些了,而且客户端是知道会话的存在的,他和服务端对于服务对象的状态是有共识的,这样可以重复的利用服务对象,直到会话结束才销毁服务对象。如果更进一步,如果创建对象的开销太大而且对象提供的服务并没有对不同客户端的差异,可以让服务端只实例化一次,这个对象将为所有的客户端服务,无论连接是不是会话,这个对象一直也不会被销毁,除非服务停止或重新启动。

综上所述,服务端实例化的方式有三种,分别是"每调用实例","每会话实例"和"单一实例"。

实例化模式的指定是通过配置服务类的ServiceBehavior属性中的InstanceContextMode属性来实现的。注意,是服务类的属性,而不是服务协定。

(1) 每调用实例

将服务类的ServiceBehavior属性中的InstanceContextMode属性设置为PerCall来启用这种模式。

在这种实例化模式下,客户端对服务方法的每一次调用,服务端都会new一个实例,方法调用结束即销毁,这种模式可能要频繁的花费创建对象和销毁对象的花销,因此性能比较差,但是每次调用后服务对象被销毁,对象所把持的资源也就被释放(如文件啦、数据库连接啦),因此伸缩性是最好的。

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]  

HelloWCFService.cs的源代码如下:

using System;
using System.ServiceModel;

namespace LearnWCF
{
    [ServiceContract(SessionMode = SessionMode.Required)]
    public interface IHelloWCF
    {
        [OperationContract]
        string HelloWCF();
    }
    
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class HelloWCFService : IHelloWCF
    {
        private int _Counter;
        public string HelloWCF()
        {
            _Counter++;
            return "Hello, you called " + _Counter.ToString() + " time(s)";
        }
    }
}

 web.config

<configuration>
  <system.serviceModel>
    <services>
      <service name="LearnWCF.HelloWCFService" behaviorConfiguration="metadataExchange">
        <endpoint address="" binding="wsHttpBinding" contract="LearnWCF.IHelloWCF"/>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="metadataExchange">
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

(2) 每会话实例

将服务类的ServiceBehavior属性的InstanceContextMode属性设置为PerSession时启用这种模式。

如果不指定,那么这个值是默认的配置。

这种模式下服务端在受到会话的第一个调用时实例化对象,直到会话关闭才销毁对象,这样减少了对象的建立和销毁开销,性能有一定提升,但是在会话期间申请的资源如文件和数据库连接直到会话结束才会释放,伸缩性有所下降了。

(3) 单一实例

把服务类的ServiceBehavior属性的InstanceContextMode属性设置为Single来启用这种模式。

在这种模式下,服务类在受到首次调用的时候实例化,然后一直不销毁,直到服务宿主关闭,在这期间无论是会话连接还是非会话连接,服务端都用这个实例进行调用。这样实际上几乎没有创建和销毁对象的开销,但是如果一旦实例把持资源,则一直都不能释放,毫无伸缩性可言。

3、总结

通过今天的学习,我们了解到了服务类实例化的几种模式,从PerCall到Single,性能越来越好,伸缩性越来越差,我们在实际项目中可以按照自己应用的特点来进行选择,同时要注意会话模式和实例化模式之间互相的影响:

(1) 如果协定不支持会话,那么PerSession相当于PerCall。

(2) 如果服务类为PerCall,那么协定的是否支持会话结果相同。

(3) 如果服务类为Single,那么协定是否支持会话结果相同。

----------WCF请求应答与单向--------------

WCF的服务端与客户端在通信时有三种模式:单向模式、请求/应答模式和双工模式。

如果选用了单向模式,调用方在向被调用方进行了调用后不期待任何回应,被调用方在执行完调用后不给调用方任何反馈。如客户端通过单向模式调用了一个服务端的操作后,就去干别的了,不会等待服务端给他任何响应,他也无从得知调用是否成功,甚至连发生了错误也全然不知。这种模式的特点是,客户端在调用操作后立即返回,从客户端角度看,用户操作的响应是非常快的,只是客户端无法得知调用结果。

 

如果选用了请求/应答模式,客户端向服务端发出调用后会一直等待服务端的回复,服务端在执行完操作后会把结果返回给客户端,即使服务操作签名返回值为void,服务端还是会返回一条空消息,告诉客户端调用完成了,客户端在接到返回后才会从调用方法返回继续进行下面的工作。这种模式的特点是客户端总是可以知道服务执行的情况,如果出错,错误也会返回,客户端对服务的执行监控的很好,但是由于在服务返回之前客户端会一直等待,所以如果服务端的服务执行时间比较长的话,客户端这边的用户响应就会很慢,如果客户端对服务的调用与用户界面在同一线程,在用户看来,应用程序就卡在那里了。

如果选用了双工模式,客户端和服务端都可以单独的向对方发送消息调用,其实这种模式是在单向模式基础上进行的,两边的调用都是单向调用,但是两边都可以独立的进行,谁也不用等待谁。

1. 如何设置消息通信模式。

双工模式有其他的设置方式,单行模式和请求应答模式的设置位置是相同的,就是通过修改操作协定的OperationContract属性的IsOneWay属性来设置。如下面的代码将HelloWCF操作协定设置为了单向模式:

    [ServiceContract]
    public interface IHelloWCF
    {
        [OperationContract(IsOneWay=true)]
        void HelloWCF();
    }  

如果不配置IsOneWay属性,那么他默认是False的,也就是说默认的消息通信模式是请求/应答模式,除非我们显式的指定为单向模式。

注意,请求应答模式是需要会话支持的,必须使用支持会话的绑定,而且服务协定的SessionMode必须至少为Allowed,服务类的ServiceBehavior的InstanceContextMode必须是PerSession,我们在这里没有配置,因为他们是默认的,但是我们必须知道他们需要这样的配置才能支持请求/应答模式。

如果你在试验中遇到了莫名其妙的问题,尝试把客户端服务引用全部删掉重新添加服务引用,因为有的时候更新服务引用不总是那么好用。

----------WCF双工通信---------

在双工模式下,服务端和客户端都可以独立地调用对方,谁都不用等待谁的答复,同样也不期待对方答复,因为如果期待答复,就变成请求/应答模式了。也就是说双方的调用都是单向调用,即我调你了,你不用回复我,你什么时候想回复我的时候呢你再调我,我就知道了,我是不会等着你的回复的。这样调用双方就会有很好的异步体验,我想调的时候就调,然后我就去干别的,什么时候调用完成了,你可以通过回调来通知我,我再决定下一步的动作,谁都不等谁。

因为在服务模型中,调用总是由客户端首先发起的,所以一般说的调用,都是客户端的行为,在双工模式下,服务器也可以调用客户端,我们就把它叫做回调,实际上这个调用和客户端的调用没什么本质区别,在回调的时候,服务端变成了客户端,客户端相当于在提供服务了,只不过总是客户端调用在前,我们就把服务端对客户端的调用叫做回调了。

1. 建立双工通信的条件

要建立双工通信,必须使用支持双工通信的绑定,比如wsHttpBinding是不支持的,必须采用wsDualHttpBinding才行,他会建立两条绑定来实现互相调用,因此我们首先要注意的是选择正确的绑定。

要建立双工通信,必须使用会话,即将SeviceContract的SessionMode配置为SessionMode.Required。

2. 如何配置双工通信

也就是说在定义上,我们需要在两边都定义两个协定接口,一个服务协定接口,一个回调协定接口,把服务协定接口的实现类写在服务端,把回调协定接口的实现类写在客户端。

通过配置服务协定的ServiceContract的CallBackContract属性来指定回调的时候使用的回调协定,考虑下面的代码:

    [ServiceContract(SessionMode = SessionMode.Required,
        CallbackContract = typeof(IHelloWCFCallback))]
    public interface IHelloWCF
    {
        [OperationContract(IsOneWay = true)]
        void HelloWCF();
    }
  
    public interface IHelloWCFCallback
    {
        [OperationContract(IsOneWay = true)]
        void Callback(string msg);
    }

同样在客户端要写出回调协定接口及其实现,如果我们使用了svcutil.exe或添加服务引用,系统会为我们自动填写回调协定接口,但是不会为我们写实现类,毕竟她不知道我们的客户端在接受回调的时候执行怎样的逻辑。我们得自己编写,一个回调协定接口的实现类看上去是这样的:

    public class HelloWCFCallback : Services.IHelloWCFCallback
    {
        public void Callback(string msg)
        {
            Console.WriteLine(msg);
        }
    }

前面提过绑定需要支持双工,因此我们需要选择一个支持双工的绑定,我们采用wsDualHttpBinding。

 <endpoint address="" binding="wsDualHttpBinding" contract="LearnWCF.IHelloWCF"/>

 总结

(1) 通信两端都有两个协定接口,接口的实现在两边一边一个。

(2) 支持双工的绑定。

(3) 指定服务协定的回调协定以建立回调联系。

(4) 客户端自己构造运行时服务类对象和实例上下文对象。

(5) 服务端通过操作上下文获得回调通道 。

(6) 需要会话的支持。

(7) 两个协定中的协定操作应为单向模式。

 

文章主要参考:

http://blog.csdn.net/songyefei/article/details/7397296

http://blog.csdn.net/songyefei/article/details/7401282

http://blog.csdn.net/songyefei/article/details/7405492

http://blog.csdn.net/songyefei/article/details/7409585

http://blog.csdn.net/songyefei/article/details/7409607