程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 我的WCF之旅(3):在WCF中實現雙向通信(Bi-directional Communication)

我的WCF之旅(3):在WCF中實現雙向通信(Bi-directional Communication)

編輯:關於.NET

昨天寫了一篇Remoting中如何實現雙向通信的文章《[原創].NET Remoting: 如何通過Remoting實現雙向通信(Bidirectional Communication) 》,作為對比,今天我們來討論一下WCF的雙向通信。

為了使我們能夠更好地對比雙向通信在Remoting中和WCF中的實現,我們的Sample采用一樣的業務邏輯——調用一個數學計算的遠程調用,除了傳遞相應的操作數之外,我們還傳遞一個對象,這個對象可以在Server端中回調 (Callback) 把運算結果在Client端顯示出來。

Step1:構建整個Solution的整體構架。

整個Solution的架構在我的之前的Blog有了詳細的介紹([原創]我的WCF之旅(1):創建一個簡單的WCF程序),這裡只作一個簡單的介紹。

Artech.WCFService.Contract: Class Library Project,用來保存Contract(Service Contact、Message Contract、Data Contract), 之所以把Contract獨立出來的原因是考慮到他同時被Server端——Service本身和Service Hosting和Client端使用

Artech.WCFService.Service:Class Library Project,Service的業務邏輯, 這個Project引用Artech.WCFService.Contract Project和System.ServiceModel DLL。

Artech.WCFService.Hosting:Console Application, 用於以Self-Hosting的方式Host Service。這個Project引用Artech.WCFService.Contract和Artech. Project WCFService.Service。Project和System.ServiceModel DLL。

Artech.WCFService.Client:Console Application, 用以模擬現實中的調用Service的Clinet。這個Project引用Artech.WCFService.Contract Project 和System.ServiceModel DLL。

http://localhost/WCFService: Web Site Project, 用於模擬如何把Service Host到IIS中。這個Project引用Artech.WCFService.Contract、Artech.WCFService.Service和System.ServiceModel DLL。

Step 2 在Artech.WCFService.Contract定義Calculator Service 和Callback的Contract

1.IDuplexCalculator.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
namespace Artech.WCFService.Contract
{
   [ServiceContract(CallbackContract = typeof(ICalculatorCallback))]
  public interface IDuplexCalculator
  {
     [OperationContract]
    void Add(double x, double y);
  }
}

2.ICalculatorCallback.cs using System;

using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
namespace Artech.WCFService.Contract
{
  [ServiceContract]
  public interface ICalculatorCallback
  {
    [OperationContract]
    void ShowResult(double x, double y, double result);
  }
}

這裡有以下幾點需要注意的:

1.在一個分布式的環境中,Client能夠調用Service,它必須知道Service的Contract, Contract定義了Service暴露給外界的所有可用的Operation,以及這些Operation的簽名(Signature).至於Service中定義的Opertion采用怎樣的實現,Client不需要了解。這也是在WCF中把Service Contract與具體的Service Implementation相互分離的一個重要原因——我們把Contract單獨提取出來,把他暴露給Client,從而可以避免把過多的暴露業務邏輯的實現。

2.在一個分布式的環境中,Serer端和Client並不是一成不變的,他們是可以相互轉化的。提供服務的就是Server,消費Service的就是Client。在這個例子中,當Artech.WCFService.Client調用Host在Artech.WCFService.Hosting中的DuplexCalculatorService(定義在 Artech.WCFService.Service中),Artech.WCFService.Client是Client,而Server端的執行環境是Artech.WCFService.Hosting。而當Calculator Service回調(Callback)Client的邏輯把運算結果顯示出來時候,因為Callback的邏輯是在Artech.WCFService.Client中執行的,所以Artech.WCFService.Client成了Server,而CalculatorCallbackHandler(定義在 Artech.WCFService.Client中)成了真正的Service。

3.我們已經說過Client能夠調用Service,它必須知道Service的Contract。所以DuplexCalculatorService能過Callback Artech.WCFService.Client,它也必須知道回調操作的Contract。WCF通過在ServiceContractAttribute中的CallbackContrac參數在制定。

[ServiceContract(CallbackContract = typeof(ICalculatorCallback))]
  public interface IDuplexCalculator
  {
     [OperationContract]
    void Add(double x, double y);
  }

Step 3 在Artech.WCFService.Service定義Duplex Calculator Service

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
  public class DuplexCalculatorService:IDuplexCalculator
  {
    IDuplexCalculator Members#region IDuplexCalculator Members
    public void Add(double x, double y)
    {
      double result = x + y;
      ICalculatorCallback callback = OperationContext.Current.GetCallbackChannel<ICalculatorCallback>();
      callback.ShowResult(x, y, result);
    }
    #endregion
  }

這裡有以下幾點需要注意的:

1.必須把並發模式ConcurrencyMode設置為ConcurencyMode. Reentrant或者ConcurencyMode.Multiple。要弄清種種的原因,我們先來看看在本例中的具體的消息交互的情況(假設我們的調用Duplex Calculator Service 和回調都采用Request/Response的MessageExcahnge Pattern,時間上一般這種情況我們應該采用One-way的ME):

首先Client調用Duplex CalculatorService, Service Request從Client到Service,Service開始執行運算,運算完成後Callback Client將運算結構在Client端顯示出來,這個過程中Service向Client發送一個Callback Message,等Client完成Callback操作後,會向Service端發送一個Callback Response(實際上是一個空的Message——以為Callback操作沒有返回值),Service收到Callback Response之後,會執行後續的操作,等所有的操作執行完畢,會發送ServiceResponse(這裡也是一個空的Message)到Client。

現在我們 來看看為什麼在建立DuplexService的時候要把並發模式設為ConcurencyMode. Reentrant或者ConcurencyMode.Multiple。在默認的並發模式下(ConcurencyMode.Single),WCF為了保證其線程安全性(ThreadSafety),在整個調用Service的過程中,InstanceContext會被WCF鎖住(Lock)。一本Sample為例,從Client向Service發送Service Request 到手的Server發回的Service Resqonse,整個InstanceContext會在Server端被鎖住, 由於在Client執行的Callback操作使用的是同一個InstanceContext, 這樣形成了一個死鎖(DeadLock)——Calculator Service必須等Callback操作完成之後才能執行後續的操作,而Callback操作必須等待InstanceContext被解鎖(Unlock)才能執行,然而InstanceContext卻被Calculator Service鎖住。

當ConcurencyMode為ConcurencyMode. Reentrant或者ConcurencyMode.Multiple的時候。當Serivice向外調用某個操作(outgoing call)的時候,或者說在向外發送一個Outgoing Message的時候,WCF會解鎖(Unlock)InstanceContext。以本例為例,Service 回調Client的時候,原來被鎖住的InstanceContext會被解鎖。這樣Callback操作就可以利用InstanceContext來執行。

2.Service可以通過OperationContext.Current.GetCallbackChannel<T>() 來或者Client在調用Calculator Service時指定的Callback Object。其中T一般被指定為Callback Contract對應的Type。

ICalculatorCallback callback = OperationContext.Current.GetCallbackChannel<ICalculatorCallback>();

Step 4 在Artech.WCFService.Hosting中Host Duplex Calculator Service

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="calculatorServieBehavior">
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service behaviorConfiguration="calculatorServieBehavior" name="Artech.WCFService.Service.DuplexCalculatorService">
        <endpoint binding="wsDualHttpBinding" contract="Artech.WCFService.Contract.IDuplexCalculator">
        </endpoint>
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:7777/DuplexCalculator" />
           </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>

Program.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using Artech.WCFService.Contract;
using Artech.WCFService.Service;
using System.ServiceModel.Description;
namespace Artech.WCFService.Hosting
{
  class Program
  {
    static void Main(string[] args)
    {
      HostDuplexCalculator();
    }
    static void HostDuplexCalculator()
    {
      using (ServiceHost calculatorSerivceHost = new ServiceHost(typeof(DuplexCalculatorService)))
      {
        calculatorSerivceHost.Opened += delegate
        {
          Console.WriteLine("Duplex calculator Service has begun to listen ");
        };
        calculatorSerivceHost.Open();
        Console.Read();
      }
    }
  }
}
這裡需要注意的時候,在Host Duplex Calculator Service 的時候,我們要為它添加相應的Endpoint。對於支持雙向通信的Service,它對Endpoint有一定的要求——我們必須為它指定一個支持Duplex MEP(Message Exchange Pattern)的Binding——比如wsDualHttpBinding,netDualTcpBinding。這裡我們使用的時wsDualHttpBinding。

Step 5 在Artech.WCFService.Client定義Callback對象和調用Duplex Calculator Service

DuplexCalculatorClient.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using Artech.WCFService.Contract;
namespace Artech.WCFService.Client
{
  class DuplexCalculatorClient:ClientBase<IDuplexCalculator>,IDuplexCalculator
  {
    public DuplexCalculatorClient(InstanceContext callbackInstance)
      : base(callbackInstance)
    { }
    IDuplexCalculator Members#region IDuplexCalculator Members
    public void Add(double x, double y)
    {
      this.Channel.Add(x, y);
    }
    #endregion
  }
}

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <system.serviceModel>
  <bindings>
   <wsDualHttpBinding>
     <binding name="wsDualBinding_IDuplexCalculator" clientBaseAddress="http://localhost:6666/myClient/" />
    </wsDualHttpBinding>
  </bindings>
  <client>
   <endpoint address="http://localhost:7777/DuplexCalculator" binding="wsDualHttpBinding"
     bindingConfiguration="wsDualBinding_IDuplexCalculator" contract="Artech.WCFService.Contract.IDuplexCalculator"
    name="duplexCalculatorEndpoint" />
   <endpoint address="http://localhost/WCFService/SessionfulCalculatorService.svc"
     binding="wsHttpBinding" bindingConfiguration="" contract="Artech.WCFService.Contract.ISessionfulCalculator" />
  </client>
 </system.serviceModel>
</configuration>

CalculatorCallbackHandler.cs

using System;
using System.Collections.Generic;
using System.Text;
using Artech.WCFService.Contract;
namespace Artech.WCFService.Client
{
  class CalculatorCallbackHandler:ICalculatorCallback
  {
    ICalculatorCallback Members#region ICalculatorCallback Members
    public void ShowResult(double x, double y, double result)
    {
      Console.WriteLine("x + y = {2} where x = {0} and y = {1}", x, y, result);
    }
    #endregion
  }
}

Program.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Artech.WCFService.Contract;
namespace Artech.WCFService.Client
{
  class Program
  {
    static void Main()
    {
      try
      { InvocateDuplexCalculator();
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
      }
      Console.Read();
    }
    static void InvocateDuplexCalculator()
    {
      CalculatorCallbackHandler callbackHandler = new CalculatorCallbackHandler();
      using(DuplexCalculatorClient calculator = new DuplexCalculatorClient(new InstanceContext(callbackHandler)))
      {
        Console.WriteLine("Begin to invocate duplex calculator ");
        calculator.Add(1, 2);
      }
    }
  }
}
這裡有以下幾點需要注意的:

1.在調用Duplex Calculator Service的時候,我們需要指定執行回調的Callback對象。在WCF中,Callback對象用一個InstanceContext對象來表示。而他在DuplexCalculatorClient的構造函數中指定。

public DuplexCalculatorClient(InstanceContext callbackInstance)
      : base(callbackInstance)
    { }

2.Client調用Duplex Calculator Service,Service端需要注冊相應的Channel來監聽來自Client的請求。同理,Service回調Client,Client也需要相應的Channel來監聽來自Service的回調。這個Channel通過下面的方式注冊。 

<wsDualHttpBinding>
     <binding name="wsDualBinding_IDuplexCalculator" clientBaseAddress="http://localhost:6666/myClient/" />
</wsDualHttpBinding>

到現在為止我們已經完成了所有的Program,我們來運行一下。

1.運行Artech.DuplexRemoting.Hosting

2.運行Artech. WCFService.Client

將Duplex Calculator Service Host 到IIS中

1.在http://localhost/WCFService中添加於Artech.WCFService.Service。DuplexCalculatorService相對應的SVC文件。

DuplexCalculatorService.svc

<%@ ServiceHost Language="C#" Debug="true" Service="Artech.WCFService.Service.DuplexCalculatorService" %>

2.並添加類似於Artech.WCFService.Hosting/App.Config中 的Configuration。

<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
  <system.serviceModel>
  <behaviors>
   <serviceBehaviors>
    <behavior name="calculatorServiceBehavior">
     <serviceMetadata httpGetEnabled="true" ></serviceMetadata>
    </behavior>
   </serviceBehaviors>
  </behaviors>
  <services>
   <service behaviorConfiguration="calculatorServiceBehavior" name="Artech.WCFService.Service.DuplexCalculatorService">
    <endpoint binding="wsDualHttpBinding" contract="Artech.WCFService.Contract.IDuplexCalculator" />
   </service>
  </services>
 </system.serviceModel>
  <system.web>
    <compilation debug="true">
      <assemblies>
        <add assembly="System.Security, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
        <add assembly="Microsoft.Transactions.Bridge, Version=3.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
        <add assembly="SMDiagnostics, Version=3.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.IdentityModel.Selectors, Version=3.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.DirectoryServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
        <add assembly="System.Web.RegularExpressions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
        <add assembly="System.Transactions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Messaging, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
        <add assembly="System.ServiceProcess, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/></assemblies></compilation>
  </system.web>
</configuration>

這樣我們可以不需要Hosting的情況下通過這樣的Uri訪問Duplex Calculator Service:http://localhost/Artech.WCFService/ DuplexCalculatorService.svc。

3.修改Client的App.Config——修正Endpoint 的Address:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <system.serviceModel>
  <bindings>
   <wsDualHttpBinding>
     <binding name="wsDualBinding_IDuplexCalculator" clientBaseAddress="http://localhost:6666/myClient/" />
    </wsDualHttpBinding>
  </bindings>
  <client>
   <endpoint address=" http://localhost/Artech.WCFService/ DuplexCalculatorService.svc " binding="wsDualHttpBinding"
     bindingConfiguration="wsDualBinding_IDuplexCalculator" contract="Artech.WCFService.Contract.IDuplexCalculator"
    name="duplexCalculatorEndpoint" />
   <endpoint address="http://localhost/WCFService/SessionfulCalculatorService.svc"
     binding="wsHttpBinding" bindingConfiguration="" contract="Artech.WCFService.Contract.ISessionfulCalculator" />
  </client>
 </system.serviceModel>

</configuration>在不起用Hosting的情況下運行Artech.WCFService.Client,我們一樣可以得到相同的結果。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved