[WCF编程]10.操做:回调操做

1、回调操做概述

    WCF支持服务将调用返回给它的客户端。在回调期间,许多方面都将颠倒过来:服务将成为客户端,客户端将编程服务。回调操做能够用在各类场景和应用程序中,但在涉及事件或者服务发生时间须要通知客户端时,显得特别有用。编程

    回调操做一般被认为是双向操做。并不是全部的绑定都支持回调操做,只有在具备了双向能力的绑定时,才支持回调操做。好比,HTTP协议本质上是与与链接无关的,因此他不能用于回调,因此,不能基于BasicHttpBingding绑定或WsHttpBingding绑定使用回调。为了让HTTP协议支持回调,WCF提供了WSDualHttpBingding绑定,它实际上设置了两个HTTP通道:一个用于从客户端到服务的调用,另外一个则是服务到客户端的调用。WCF也为NetTcpBingding和NetNamePipeBingding绑定提供了对回调操做的支持。因此,TCP和IPC协议均支持双向通讯。安全

    双工回调并非标准的操做,由于没有对于的行业标准来规定客户端如何传递客户端地址给服务,或者服务如何实现发布回调契约。双工回调会损害WCF的性能。不多用到WSDualHttpBingding,由于它没法穿越客户端和服务端的重重阻碍。这种链接线问题由Windows  Azure AppFabric Service Bus解决了,在云上它支持双工对调,使用NetTcpRelayBingding绑定。多线程

2、回调操做5步骤

1.建立标准契约和回调契约

    /// <summary>
    /// 回调契约
    /// </summary>
    public interface IMyContractCallback
    {
        [OperationContract]
        void OnCallback(string name);
    }


    [ServiceContract(CallbackContract = typeof(IMyContractCallback))]
    public interface IMyContract
    {
        [OperationContract]
        void DoSomething(string name);
    }

2.在服务端实现标准契约

[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant)]
    public class MyContract : IMyContract
    {
        private IMyContractCallback callback;
        public void DoSomething(string name)
        {
            callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>();
            callback.OnCallback(name + "回来了");
        }
    }

3.建立服务端承载

class Program
    {
        static void Main(string[] args)
        {
            ServiceHost host = new ServiceHost(typeof(WCF.CallbackOperation.Service.MyContract));
            host.Open();
            Console.WriteLine("服务启动成功......");
            int i = 0;
            foreach (ServiceEndpoint endpoint in host.Description.Endpoints)
            {
                i++;
                Console.WriteLine("终结点序号:{0},终结点名称:{1},终结点地址:{2},终结点绑定:{3}{4}", i, endpoint.Name, endpoint.Address, endpoint.Binding, Environment.NewLine);
            }

            Console.Read();
        }
    }
    App.config
<system.serviceModel>
    <services>
      <service name="WCF.CallbackOperation.Service.MyContract" behaviorConfiguration="CallbackTcpBehavior">
        <endpoint name="tcp_IMyContract" address="MyContract" contract="WCF.CallbackOperation.Service.IMyContract" binding="netTcpBinding"></endpoint>
        <endpoint address="MyContract/mex" binding="mexTcpBinding" contract="IMetadataExchange"></endpoint>
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://127.0.0.1:9000"/>
          </baseAddresses>
        </host>
      </service>

    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="CallbackTcpBehavior">
          <serviceMetadata httpGetEnabled="false" httpsGetEnabled="false"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <diagnostics performanceCounters="All"/>
  </system.serviceModel>

4.实现客户端代理

    使用Visual Studio的SvcUtil工获取并发

5.在客户端实现并调用

class Program
    {
        static void Main(string[] args)
        {
            MyCallback callback = new MyCallback();
            callback.CallService();
            callback.Dispose();

            Console.Read();
        }

        class MyCallback : IMyContractCallback, IDisposable
        {
            private MyContractClient m_Proxy;

            public void CallService()
            {
                InstanceContext context = new InstanceContext(this);
                m_Proxy = new MyContractClient(context);
                m_Proxy.DoSomething("zxj");
            }

            public void OnCallback(string name)
            {
                Console.WriteLine(name);
            }

            public void Dispose()
            {
                m_Proxy.Close();
            }
        }
    }

    实例代码下载:下载tcp

 

3、回调契约

    回调操做是服务契约的一部分,它取决于服务契约对回调契约的定义。一个服务契约最多只能包含一个回调契约。一旦定义了回调契约,就须要客户端支持回调,并在每次调用中提供执行服务的回调终结点。若要定义回调契约,则可使用ServiceContract特性提供的Type类型的属性CallbackContract。函数

public sealed class ServiceContractAttribute : Attribute
{
     public Type CallbackContract { get; set; }
}

    在定义包含回调契约的服务契约时,须要为ServiceContract提供回调契约的类型,以及回调契约的定义,以下所示:性能

 public interface ISomeCallbackContract
 {
    [OperationContract]
     void OnCallback();
 }

 [ServiceContract(CallbackContract=typeof(ISomeCallbackContract))]
 public interface IMyContract
 {
     [OperationContract]
     void DoSomething();
 }

    注意,回调契约必须标记ServiceContract特性。由于只要类型定义为回调契约,就意味着它具备ServiceContract特性,而且在服务元数据中也将包含该特性。固然,咱们仍然腰围全部的回调接口方法标记OperationContract特性。this

    当客户端导入的回调接口与原来的服务端定义的名称不一样时,它会被修改成服务契约的接口名后将后缀Callback。例如,若是客户端导入上个例子的定义,则客户端会得到如下的定义:spa

    /// <summary>
    /// 回调契约
    /// </summary>
    public interface IMyContractCallback
    {
        [OperationContract]
        void OnCallback(string name);
    }


    [ServiceContract(CallbackContract = typeof(IMyContractCallback))]
    public interface IMyContract
    {
        [OperationContract]
        void DoSomething(string name);
    }

    因此,建议在服务端使用命名规范,即在回调契约命名为服务契约接口后将后缀Callback。线程

4、客户端回调设置

    客户端负责托管回调对象以及公开回调终结点。服务实例最内层的执行范围是实例上下文。InstanceContext类定义的构造函数可以将服务实例传递给宿主。

public sealed class InstanceContext : CommunicationObject, IExtensibleObject<InstanceContext>
{
    // 实现服务实例的对象。
    public InstanceContext(object implementation);
    // 为实例上下文返回服务的实例。
    public object GetServiceInstance();
    ......
}

    为了托管一个回调对象,客户端须要实例化回调对象,而后经过它建立一个上下文对象:

private MyContractClient m_Proxy;

public void CallService()
{
    InstanceContext context = new InstanceContext(this);
    m_Proxy = new MyContractClient(context);
    m_Proxy.DoSomething("zxj");
}

    值得一提的是,虽然回调方法是在客户端,但它们仍然属于WCF操做,所以他们都是一个操做调用上下文,经过OperationContext.Current访问。

双向代理

    不管什么时候,只要服务终结点的契约定义了一个回调契约,客户端在与它进行交互时,都必须使用代理建立双向通讯,并将回调终结点的引用传递给服务。所以没客户端使用的代理必须继承一个专门的代理类DuplexClientBase<T>,以下所示:

public abstract class DuplexClientBase<TChannel> : ClientBase<TChannel> where TChannel : class
{
    protected DuplexClientBase(InstanceContext callbackInstance);
    protected DuplexClientBase(object callbackInstance);
    protected DuplexClientBase(InstanceContext callbackInstance, ServiceEndpoint endpoint);
    protected DuplexClientBase(object callbackInstance, ServiceEndpoint endpoint);
    protected DuplexClientBase(InstanceContext callbackInstance, Binding binding, EndpointAddress remoteAddress);
    protected DuplexClientBase(object callbackInstance, Binding binding, EndpointAddress remoteAddress);
    //  获取内部双工通道。
    public IDuplexContextChannel InnerDuplexChannel { get; }
    ......
}

    客户端须要提供实例上下文绑定给托管宿主的DuplexClientBase<T>构造函数。代理会根据回调上下文构建终结点,从服务终结点配置信息里推断回调终结点的信息:回调终结点契约是经过服务契约回调类型定义的。回调终结点会使用与外发调用相同的绑定。对于地址,WCF会使用客户端机器名。只是简单地传递实例上下文给双向代理,并使用代理来调用服务暴露的终结点。为了简化这个过程,DuplexClientBase<T>提供了能够接受回调对象的构函数,并将其包装在上下文里。不管出于什么缘由,当客户端须要访问上下文时,DuplexClientBase<T>都会另外提供IDuplexContextChannel类型的InnerDuplexChannel属性。它提供了经过InnerDuplexChannel属性访问上下文的方式。

    使用Visual Studio可以为包含了回调契约的目标服务生成代理类,生成的类派生自DuplexClientBase<T>,以下所示:

public partial class MyContractClient : System.ServiceModel.DuplexClientBase<IMyContract>, IMyContract
{
    
    public MyContractClient(System.ServiceModel.InstanceContext callbackInstance) : 
            base(callbackInstance)
    {
    }
    
    public MyContractClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName) : 
            base(callbackInstance, endpointConfigurationName)
    {
    }
    
    public MyContractClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress) : 
            base(callbackInstance, endpointConfigurationName, remoteAddress)
    {
    }
    
    public MyContractClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : 
            base(callbackInstance, endpointConfigurationName, remoteAddress)
    {
    }
    
    public MyContractClient(System.ServiceModel.InstanceContext callbackInstance, System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : 
            base(callbackInstance, binding, remoteAddress)
    {
    }
    
    public void DoSomething()
    {
        base.Channel.DoSomething();
    }
    
    public System.Threading.Tasks.Task DoSomethingAsync()
    {
        return base.Channel.DoSomethingAsync();
    }
}

    客户端可使用派生的代理类建立回调实例,并将上下文做为它的宿主,建立代理、调用代理服务,这样就能够传递回调终结点的引用。

    class MyCallback : IMyContractCallback, IDisposable
    {
        private MyContractClient m_Proxy;

        public void CallService()
        {
            InstanceContext context = new InstanceContext(this);
            m_Proxy = new MyContractClient(context);
            m_Proxy.DoSomething("zxj");
        }

        public void OnCallback(string name)
        {
            Console.WriteLine(name);
        }

    }

    注意,只要客户端正在等待回调,就不能关闭代理。若是关闭回调终结点,当服务试图将调用返回时,就会致使服务端产生错误。

    最多见的实现方式是由客户端自身实现回调契约,此时,客户端一般能够将代理定义为成员变量,并在释放客户端时关闭它,以下所示:

class MyCallback : IMyContractCallback,IDisposable
{
    private MyContractClient m_Proxy;
     public void CallService()
    {
        InstanceContext context = new InstanceContext(this);
        m_Proxy = new MyContractClient(context);
        m_Proxy.DoSomething();
    }
    public void OnCallback()
    {
        throw new NotImplementedException();
    }
     public void Dispose()
    {
        m_Proxy.Close();
    }
}

 

5、服务端的回调调用

    随着服务端的每一次调用,客户端的回调终结点引用都会被传递到服务,组成传入消息的一部分。OperationContext类为服务提供了方便访问回调引用的途径,即调用泛型方法GetCallbackChannel<T>();

public sealed class OperationContext : IExtensibleObject<OperationContext>
{
    // 获取调用当前操做的客户端实例的通道。
    public T GetCallbackChannel<T>();
}

    服务如何处理回调引用及什么时候决定使用它,彻底由服务选择,这一点毋庸置疑。服务可以从操做上下文中提取出回调引用,而后将它保存起来以备往后使用;或者能够在服务运行期间使用它,将调用返回客户端。以下所示:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class MyContract : IMyContract
{
   private IMyContractCallback callback;
    public void DoSomething(string name)
    {
       callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>();
        callback.OnCallback(name + "回来了");
    }
}

回调重入

    服务可能须要调用传入的回调引用。然而,这样的调用在默认状况下是禁止的,由于它受默认的服务并发管理的限制。在默认状况下,服务类被配置为单线程访问:服务实例上下文与锁关联,在任什么时候刻都只能有一个线程拥有锁,也只能有一个个线程可以访问上下文中的服务实例。在操做调用期间,在客户端发布的调用须要阻塞服务线程,并调用回调。问题是一旦回调返回它须要的重入的相同上下文及获取同一个锁的全部权,处理从相同通道的客户端返回的应答消息时就会致使死锁。注意,服务仍然可能调用到其它客户端的回调,或者调用其余服务,这属于正在调用的会致使死锁的客户端的回调。

    若是单线程的服务实例试图调用返回给他的客户端,为了不死锁,WCF会抛出InvilidOperationException异常。对于这种状况,有三种可能的解决方案:

    第一种方案是配置服务,容许多线程访问。因为服务实例与锁无关,所以容许正在调用的客户端回调。可是这种状况也可能增长服务开发者的负担,由于它须要为服务提供同步。

    第二种方案是将服务配置为重入。一旦配置为重入,服务实例就与锁管理,同时只容许单线程访问。若是服务正在回调它的客户端,WCF就会先释放锁。就目前而言,若是服务须要回调它的客户端,则可使用ServiceBehavior特性的ConcurrencyMode属性,将服务的并发行为配置为多线程或者重入。

[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant)]
public class MyContract : IMyContract
{
    private IMyContractCallback callback;
    public void DoSomething(string name)
    {
        callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>();
        callback.OnCallback(name + "回来了");
    }
}

    第三种方案是将回调契约操做配置为单向操做,这样服务就可以安全地将调用返回给客户端。由于没有任何应答消息会竞用锁,即便并发模式设置为单线程,服务也可以支持回调。注意,实例代码中使用第二种方案,如使用第三种方案,请从新生成客户端代理类。

public interface IMyContractCallback
{
    [OperationContract(IsOneWay = true)]
    void OnCallback(string name);
}

[ServiceContract(CallbackContract = typeof(IMyContractCallback))]
public interface IMyContract
{
    [OperationContract]
    void DoSomething(string name);
}

public class MyContract : IMyContract
{
    private IMyContractCallback callback;
    public void DoSomething(string name)
    {
        callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>();
        callback.OnCallback(name + "回来了");
    }
}

6、回调链接管理

    若是客户端保持打开状态,则服务只能将调用返回给客户端。一般状况下,这创建在代理没有关闭的状况下。保证代理处于打开状态一样能够避免回调对象被垃圾回收期回收。若是服务维持了一个在对调终极点上的引用,并且客户端代理是关闭的,或者客户端应用程序已经推出,那么当服务调用回调时,就会从服务通道处获取一个ObjectDisposedException异常。所以,对于客户端而言,当它再也不须要接收回调会客户端应用已经关闭,最好可以通知服务无。为此,能够在服务契约中显式添加一个Disconnect()方法。若是每一个放哪广发调用都担心回调引用,那么服务就可以在Disconnect()方法中将回调从内部存储结构中移除。

    固然,也建议开发者同时在服务契约中也显式提供一个Connect()方法。定义Connect()方法,可以是客户端重复地链接或断开,同时还提供一个明确的时间点,以判断什么时候须要一个回调,由于回调只可以发生在调用Connect()方法以后。

    [ServiceContract(CallbackContract = typeof(IMyContractCallback))]
    public interface IMyContract
    {
        [OperationContract]
        void DoSomething(string name);

        [OperationContract]
        void Connect();

        [OperationContract]
        void DisConnect();
    }


    [ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant)]
    public class MyContract : IMyContract
    {
        private IMyContractCallback callback;
        public void DoSomething(string name)
        {
            callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>();
            callback.OnCallback(name + "回来了");
        }

        public void Connect()
        {
            //能够实现为回调列表,将回调引用添加到列表中
        }

        public void DisConnect()
        {
            //能够实现为回调列表,将回调引用从列表中移除
        }
    }

链接管理与实例操做

单向服务

    只有在操做调用回调引用对象自身或者将它存储在某些全局变量(如静态变量)时,单调服务才可以使用 回调引用。既然服务可能使用的存储在引用对象中的任何实例状态在操做返回时都会丢失,那么单调服务就必须使用某些静态变量用以存储回调引用。于是,单调服务特别须要相似于Disconnect()方法。若是没有该放哪广发,共享存储就会随着时间的推移,而出现大量冗余的回调引用。

单例服务

    固然,单例服务也存在相似的状况。由与单例对象的生命周期不会结束,于是它会无限累计回调引用,随着时间的推移,回调的客户端不服存在,大多数回调引用也随之失效。定义DisConnect()方法能够保证单例服务只链接到对应的活动客户端。

会话服务

    会话服务即便没有DisConnect()方法也能得到引用,只要它将回调引用保存在某个实例成员的变量中。缘由在于当会话结束时,服务实例会被自动释放,整个会话不存在保持引用的危险。这就可以保证回调引用老是有效的。可是,若是会话服务为了让其它宿主段或跨会话访问而将它的回调引用保存在某个全局变量中,必须添加DisConnect()方法,已达到移除回调引用的目的,这就是由于在调用Dispose()期间,不能使用回调引用。

    咱们也能够为会话服务添加Connect()和DisConnect()方法,由于它容许客户端在会话期间决定什么时候启动或中止接受回调消息。

7、回调契约层级

    设计回调契约时,须要注意设计上的约束。若是一个服务契约的基契约可回调接口,则服务契约定义的回调结构必须是它的契约定义的全部回调接口的子接口。以下,如下回调契约是无效的:

interface ICallbackContract1
{......}

interface ICallbackContract2
{......}

[ServiceContract(CallbackContract = typeof(ICallbackContract1))]
interface IMyBaseContract
{......}

//无效
[ServiceContract(CallbackContract = typeof(ICallbackContract2))]
interface IMySubContract
{......}

    IMySubContract不能指定ICanllbackContract2为它的回调契约,由于ICallbackContract2不能ICallbackContract1的子类,而IMyBaseContract(IMySubContract的父接口)定义了ICallbackContract1做为它的回调接口。

    知足约束的最直接方法实在回调契约层级反映服务契约的层次:

interface ICallbackContract1
{......}

interface ICallbackContract2:ICallbackContract1
{......}

[ServiceContract(CallbackContract = typeof(ICallbackContract1))]
interface IMyBaseContract
{......}

[ServiceContract(CallbackContract = typeof(ICallbackContract2))]
interface IMySubContract
{......}
相关文章
相关标签/搜索