程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WCF分布式開發步步為贏(10)

WCF分布式開發步步為贏(10)

編輯:關於.NET

請求應答(Request-Reply)、單向操作(One-Way)、回調操作(Call Back)

WCF除了支持經典的請求應答(Request-Reply)模式外,還提供了什麼操作調用模式,他們有什麼不同以及我們如何在開發中使用這些操作調用模式。今天本節文章裡會詳細介紹。WCF分布式開發步步為贏(10):請求應答(Request-Reply)、單向操作(One-Way)、回調操作(Call Back).本文結構:【1】請求應答(Request-Reply)、【2】單向操作(One-Way)、【3】回調操作(Call Back)、【4】示例代碼分析、【5】總結。最後上傳本文的示例代碼。

WCF除了支持經典的請求/應答模式意外,還提供了對單向操作、雙向回調操作模式的支持,此外還有流操作(後者與WSE3.0提供的優化傳輸機制類似,我曾經在這個文章裡進行過講解WSE3.0構建Web服務安全(4):MTOM消息傳輸優化和文件上傳、下載)。今天我們會介紹幾種操作調用模式的概念,區別,實現機制,以及如何在代碼中實現他們,最後給出的要注意的細節問題。

【1】請求應答(Request-Reply):

請求應答模式是默認的操作模式。這與經典的C/S編程類似,客戶端發送請求,阻塞客戶端進程,服務端返回操作結果。請求應答模式與綁定對應關系 :

除了NetPeerTcpBinding和NetMsmqBinding綁定,所有的綁定均支持請求-應答操作。

【2】單向操作(One-Way):

【2.1】概念:

簡單來說,單向操作沒有返回值,客戶端只管調用,不管結果。單向操作客戶端一旦發出請求,WCF會生成一個請求,不會給客戶端返回任何消息。單向操作不同於異步操作,雖然單向操作只是在發出調用的瞬間阻塞客戶端,但如果發出多個單向調用,WCF會將請求調用放入隊列,並在某個時候執行。隊列存儲調用的個數是有限的,一旦發出的調用個數超出了隊列存儲調用的設置值,則會發生阻塞現象,因為調用無法放入隊列。當隊列的請求出列後,產生阻塞的調用就會放入隊列,並解除對客戶端的阻塞。綁定協議與單向請求模式關系:

和請求應答模式不同。所有的WCF綁定通信協議都支持單向操作。

【2.2】實現方式:

配置單向操作的方式也很簡單,WCF的OperationContract 定義了IsOneWay屬性。我們設置設置單向操作的方法是利用OperationContract特性的IsOneWay屬性,例如:

//操作契約,單調操作,不返回應答消息,會話服務中,保證是最後一個操作
[OperationContract(IsOneWay=true,IsInitiating=false,IsTerminating=true)]//
void SayHello2(string name);

單向操作配置的屬性定義在操作契約級別上。而不是用在服務契約級別。

【2.3】單向操作小節:

(1)被設置為單向操作的方法不能包含返回值,即它的返回值只能為void,否則會拋出InvalidOperationException異常。

(2)在會話契約中雖然允許定義單向操作([ServiceContract( SessionMode =SessionMode.Required, Namespace = "http://www.cnblogs.com/frank_xl/")]),但由於單向操作服務端管理客戶端會話狀態十分困難,因而,單向操作的最佳適用場景是在單調服務或單例服務中。如果在會話契約中定義了單向操作,就必須保證單向操作是終止會話的最後一個操作,返回void類型值。這可以通過分步操作來實現。代碼如下:

//1.單向服務契約,會話服務
[ServiceContract( SessionMode =SessionMode.Required, Namespace = "http://www.cnblogs.com/frank_xl/")]
public interface IWCFServiceOneWay
{
  //操作契約,單調操作,不返回應答消息,會話服務中,保證是最後一個操作
  [OperationContract(IsOneWay=true,IsInitiating=false,IsTerminating=true)]//
  void SayHello2(string name);
  //操作契約,
  [OperationContract]
  string SayHello1(string name);

}

(3)如果因為通信(地址宿主)問題,調用操作失敗,單向操作如果拋出異常;客戶端受服務端異常影響,取決於實例模式以及使用綁定。

【3】回調操作(Call Back):

【3.1】概念:

回調不是一個新的概念,早在C語言裡就有過,C#裡更是有委托實現回調機制。軟件模塊之間總是存在著一定的接口,從調用方式上,可以把他們分為三類:同步調用、回調和異步調用。同步調用是一種阻塞式調用,調用方要等待對方執行完畢才返回,它是一種單向調用;回調是一種雙向調用模式,也就是說,被調用方在接口被調用時也會調用對方的接口;異步調用是一種類似消息或事件的機制,不過它的調用方向剛好相反,接口的服務在收到某種訊息或發生某種事件時,會主動通知客戶方(即調用客戶方的接口)。回調和異步調用的關系非常緊密,通常我們使用回調來實現異步消息的注冊,通過異步調用來實現消息的通知。同步調用是三者當中最簡單的,而回調又常常是異步調用的基礎,因此,下面我們著重討論回調機制在WCF軟件架構中的實現。回調機制如圖所示:

並非所有的綁定協議都支持回調,http本質上是無連接的協議,TCP/IP協議才會在客戶端和服務端維持通信信道。兩者之間的對應關系如下:

BasicHttpBinding,WSHttpBinding綁定協議不支持回調操作。NetTcpBinding和NetNamedPipeBinding綁定支持回調操作;具有可靠消息傳輸的WSHttpBinding綁定是通過設置兩個HTTP信道來支持雙向通信。

【3.2】實現代碼:

一個服務契約只能包含一個回調契約。通過ServiceContract特性,可以指定回調契約:

//0.回調服務契約,由於回調方法在客戶端執行,因此無須添加 ServiceContractAttribute。對於回調操作,服務器無須獲取其返回信息,因此添加 IsOneWay=true 特性參數。
public interface IWCFServiceCallBack
{
 //操作契約
 [OperationContract(IsOneWay=true)]//
 void SayHelloCalllBack();
}
//1.服務契約,指定 SessionMode 和回調類型。
[ServiceContract(SessionMode = SessionMode.Required,CallbackContract = typeof(IWCFServiceCallBack))]
public interface IWCFService
{
 //操作契約,
 [OperationContract]
 string SayHelloToUser(string name);

}

回調契約無須標記ServiceContract特性,但是在回調契約中必須為服務的操作標記OperationContract特性。

在導入回調契約的元數據中,回調契約以Callback結尾。服務端反序列化本地代碼的時候會生成客戶端回調操作契約Callback後綴。

【3.3】回調小節:

(1)如果使用了回調契約,回調契約不需要ServiceContract特性,設置為回調契約就默認了服務契約的特性。

(2)客戶端通過回調傳遞給服務端的消息包含了回調契約終結點的引用。在服務端,可以通過OperationContext類的泛型方法GetCallbackChannel<T>()獲得。代碼如下: 

//獲取客戶端通道實例
IWCFServiceCallBack callback = OperationContext.Current.GetCallbackChannel<IWCFServiceCallBack>();

【4】示例代碼分析:

直接看概念還不能很好的理解回調的機制,下面我們來具體看看WCF裡如何實現回調。客戶端調用服務操作,服務操作通過客戶端上下文實例調用客戶端操作,這是回調操作的基本過程。一下是具體的代碼實現講解過程。這裡只介紹回調操作的具體實現代碼。單向操作過於簡單,注釋也比較詳細,大家可以參考上傳的代碼。

【4.1】服務端:

定義一個回調契約IWCFServiceCallBack,服務契約IWCFService、服務類WCFService : IWCFService繼承服務契約。代碼如下:

//1.回調服務契約,由於回調方法在客戶端執行,因此無須添加 ServiceContractAttribute。對於回調操作,服務器無須獲取其返回信息,因此添加 IsOneWay=true 特性參數。
public interface IWCFServiceCallBack
{
  //操作契約
  [OperationContract()]//
  void SayHelloCalllBack();
}
//2.服務契約,指定 CallbackContract 回調契約。
[ServiceContract(CallbackContract = typeof(IWCFServiceCallBack))]
public interface IWCFService
{
 //操作契約,
 [OperationContract]
 string SayHelloToUser(string name);

}
//3.服務類,繼承接口。實現服務契約定義的操作
public class WCFService : IWCFService
{
  //獲取當前操作客戶端對象實例
  IWCFServiceCallBack callback = OperationContext.Current.GetCallbackChannel<IWCFServiceCallBack>();
  //實現接口定義的方法
  public string SayHelloToUser(string name)
  {
   //Action<IWCFServiceCallBack> invoke = delegate(IWCFServiceCallBack callBack)
   //{ callBack.SayHelloCalllBack(); };
   ////callback(invoke);
   Console.WriteLine("Hello! {0} , ", name);
   callback.SayHelloCalllBack();
   return "Hello! " + name;
  }
}

服務端 獲取當前操作客戶端對象實例

IWCFServiceCallBack callback = OperationContext.Current.GetCallbackChannel<IWCFServiceCallBack>();callback.SayHelloCalllBack();執行回調客戶端當前實例方法。

【4.2】宿主:

宿主啟動和綁定節點配置和前面幾節講解的配置過程類似。這裡配置的協議是TCP。配置文件代碼如下:

<service behaviorConfiguration="WCFService.WCFServiceBehavior" name="WCFService.WCFService">
         <endpoint
           address="net.tcp://localhost:9004/WCFService"
           binding="netTcpBinding"
           contract="WCFService.IWCFService">
         </endpoint>
         <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
         <host>
           <baseAddresses>
             <add baseAddress="http://localhost:9003/"/>
             <add baseAddress="net.tcp://localhost:9004/"/>
           </baseAddresses>
         </host>
</service>

【4.3】客戶端:

運行服務托管宿主,客戶端添加服務引用,反序列化服務元數據,如圖:

修改客戶端代碼,重新實現回調契約的操作方法,如下:

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public class WCFServiceCallback : IWCFServiceCallback
{
 public void SayHelloCalllBack()
 {
   Console.WriteLine("Client method is CallBacking");
 }
}

測試回調代碼,我們實例化一個回調類的實例,然後作為上下文實例的參數。最後把上下文作為參數實例化一個客戶端代理。具體代碼如下:

//CallBack 回調服務
Console.WriteLine("Call Back Operation Test");
WCFClientCallBack.IWCFServiceCallback callBack = new WCFClientCallBack.WCFServiceCallback();

InstanceContext context = new InstanceContext(callBack);
WCFClientCallBack.WCFServiceClient WCFServiceCallBackClientProxy = new WCFClientCallBack.WCFServiceClient(context, "NetTcpBinding_IWCFService");
//通過代理調用調用SayHelloToUser,傳遞對象
Console.WriteLine(WCFServiceCallBackClientProxy.SayHelloToUser("Frank Xu Lei Call Back"));

【4.4】運行結果:

這裡的運行結果包括單向操作和回調操作結果,客戶端調用一個服務操作,服務操作再通過客戶端上下文實例引用調用客戶端操作。成功執行回調操作。結果如圖:

【5】總結:

(1) 服務對回調的調用可能會產生死鎖。就是指當回調的應答消息也需要獲得與服務實例關聯的相同的鎖時,會導致死鎖。此時服務線程已經被阻塞,服務操作正在等待回調操作執行完畢,而回調操作卻又在等待服務釋放鎖。解決死鎖的辦法:<1>將服務配置為允許多線程訪問,會增加服務開發者負擔。

<2>將回調設置為重入(Reentrancy) [ServiceContract(ConcurrencyMode=ConcurrencyMode.Reentrant,CallbackContract = typeof(IWCFServiceCallBack))]

。所謂“重入”,是指對同步域擁有獨占訪問權的線程A調用了同步域之外對象的方法,此時,另外的線程B若要訪問該同步域,則線程A將釋放對同步域的鎖,允許線程B進入。直到線程B執行完畢並釋放對同步域的鎖後,線程A將重新進入該同步域。由於服務被配置為重入,則服務調用回調引用時會釋放鎖。然後將回調返回給客戶端,控制權則返回給服務,服務會重入並重新獲取鎖。

<3>將回調操作設置為單向操作( [OperationContract(IsOneWay=true)]// void SayHelloCalllBack();)。此時,回調調用不會產生應答消息,服務操作一旦執行了回調操作,就會繼續執行,回調對象不會爭用與服務實例關聯的鎖,從而解決了死鎖問題。

(2)單調服務的回調問題需要考慮回調對象的引用存儲問題,因為每次調用結束都會釋放服務實例對象,客戶端的狀態也會丟失。

(3)單例服務的問題是,猶豫所有的服務共享一個實例,而其生命周期的問題,回調的引用計數會增加。

(4)回調契約的層級問題,一旦一個服務契約提供了回調契約的定義,其所有的子接口必須生命和其一致的回調契約。

(5)NetTcpBinding和NetNamedPipeBinding綁定支持回調操作,並且和客戶端共享一個通道端口;具有可靠消息傳輸的WSHttpBinding需要維護連個端口有可能產生端口沖突,編程時值得注意。

以上就是本節的全部內容,下一節我們會介紹WCF流操作的一些內容,這裡也值得學習。因為在前面的WSE3.0文章裡我介紹了WSE優化文件傳輸的問題。當時也提到了流操作的概念。WCF框架也提供了流操作的支持,同樣值得我們學習。這裡對大規模數據對象的操作和處理有重要作用,下一個准備介紹一下。

本文配套源碼

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