WCF學習之旅—請求與答復模式和單向模式(十九)
四、HTTP雙工模式
雙工模式建立在上文所實現的兩種模式的基礎之上,實現客戶端與服務端相互調用:前面介紹的兩種方法只是在客戶端調用服務端的方法,然后服務端有返回值返回客戶端;相互調用是指客戶端調用服務端的方法,同時服務端也可以調用客戶端的方法。
基于雙工MEP (信息交換模式,Message Exchange Pattern,下同)消息交換可以看成是多個基本模式下 (比如請求-回復模式和單項模式)消息交換的組合。雙工MEP又具有一些變體,比如典型的訂閱-發布模式就可以看成是雙工模式的一種表現形式。
一) 兩種典型的雙工MEP
1.請求過程中的回調
這是一種比較典型的雙工消息交換模式的表現形式,客戶端在進行服務調用的時候,附加上一個回調對象;服務在對處理該處理中,通過客戶端附加的回調對 象(實際上是調用回調服務的代理對象)回調客戶端的操作(該操作在客戶端執行)。整個消息交換的過程實際上由兩個基本的消息交換構成,其一是客戶端正常的服務請求,其二則是服務端對客戶端的回調。兩者可以采用請求-回復模式,也可以采用單向(One-way)的MEP進行消息交換。圖1描述了這樣的過程,服務調用和回調都采用請求-回復。
圖1 請求過程中的回調
2.訂閱-發布
訂閱-發布模式是雙工模式的一個典型的變體。在這個模式下,消息交換的雙方變成了訂閱者和發布者,若干訂閱者就某個主題向發布者申請訂閱,發布者將 所有的訂閱者保存在一個訂閱者列表中,在某個時刻將主題發送給該主題的所有訂閱者。實際上基于訂閱-發布模式的消息交換也可以看成是兩個基本模式下消息交 換的組合,申請訂閱是一個單向模式的消息交換(如果訂閱者行為得到訂閱的回饋,該消息交換也可以采用請求-回復模式);而主題發布也是一個基于單向模式的消息交換過程。訂閱-發布消息交換模式如圖2所示。
圖2 訂閱-發布
二) 實例演示:創建基于雙工通信的WCF應用
接下來我們通過一個的實例來學習基于雙工通信的WCF應用。為簡單起見,我們沿用上文( WCF學習之旅—請求與答復模式和單向模式(十九))的示例。在上文的示例中,我們都是調用 BookService直接顯示書籍名稱,并將結果通過Winform應用程序輸出。在本例中我們將采用另外一種截然不同的方式調用服務并進行結果的輸出:我們通過單向(One-way)的模式調用BookService(也就是客戶端不可能通過回復消息得到結果),服務端在完成書籍名稱顯示之后,通過回調(Callback)的方式在客戶端將書籍名稱顯示出來。
步驟一:定義服務契約和回調契約
首先進行服務契約的定義,我們照例通過接口(IBookService)的方式定義服務契約,作用于指定顯示書籍名稱的方法DisplayName操作,我們通過OperationContractAttribute特性的IsOneway屬性將操作定義成單向的操作,這意味著客戶端僅僅是向服務端發送一個請求,并不會通過回復消息得到任何結果。
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace WcfServiceLib { // 注意: 使用“重構”菜單上的“重命名”命令,可以同時更改代碼和配置文件中的接口名“IBookService”。 [ServiceContract(CallbackContract = typeof(ICallback))] public interface IBookService { /// <summary> /// 單工模式,顯示名稱 /// </summary> /// <param name="name">書籍名稱</param> [OperationContract(IsOneWay = true)] void DisplayName(string name); } }
我們要實現的功能是通過在服務端回調客戶端的操作方式實現結果的輸出。客戶端正常調用BookService的服務方法,那么在服務 執行過程中借助于客戶端在服務調用時提供的回調對象對客戶端的操作進行回調,從本質上講是另外一種形式的服務調用。WCF采用基于服務契約的調用形式,客戶端正常的服務調用需要服務契約,同理服務端回調客戶端依然需要通過描述回調操作的服務契約,我們把這種服務契約稱為回調契約。回調契約的類型通過ServiceContractAttribute特性的CallbackContract屬性進行指定。
上面代碼中服務契約IBookService的回調契約ICallback定義如下。由于回調契約本質也是一個服務契約,所以定義方式和一般意義上的 服務契約基本一樣。有一點不同的是,由于定義IBookService的時候已經通過 [ServiceContract(CallbackContract=typeof(ICallback))]指明ICallback是一個服務契約 了,所以ICallback不再需要添加ServiceContractAttribute特性。ICallback定義了一個服務操作DisplayResult用于顯示書籍名稱與日期,由于服務端不需要回調的返回值,所以將回調操作也設為單向方法。ICallback接口代碼如下。
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace WcfServiceLib { public interface ICallback { [OperationContract(IsOneWay = true)] void DisplayResult(string result); } }
步驟二:實現服務
在BookService實現了上面定義的服務契約IBookService中的方法,實現了DisplayName操作,完成書籍名稱和日期的顯示工作。同時把書籍名稱與日期的顯示通過回調的方式在客戶端顯示出來,所以需要借助于客戶端提供的回調對象(該對象在客戶端調用BookService的時候指定,在介紹客戶端代碼的實現的時候會講到)。在WCF中,回調對象通過當前OperationContext的GetCallback<T>方法獲得(T代表回調契約的類型)。代碼如下。
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace WcfServiceLib { // 注意: 使用“重構”菜單上的“重命名”命令,可以同時更改代碼、svc 和配置文件中的類名“BookService”。 // 注意: 為了啟動 WCF 測試客戶端以測試此服務,請在解決方案資源管理器中選擇 BookService.svc 或 BookService.svc.cs,然后開始調試。 public class BookService : IBookService { /// <summary> /// 雙工模式,回調顯示結果 /// </summary> /// <param name="name">名稱</param> public void DisplayName(string name) { string result=string.Format("書籍名稱:{0},日期時間{1}", name, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); Console.WriteLine("\r\n" + result); ICallback call = OperationContext.Current.GetCallbackChannel<ICallback>(); call.DisplayResult("回調---"+result); } } }
注: OperationContext在WCF中是一個非常重要、也是一個十分有用的對象,它代表服務操作執行的上下文。我們可以通過靜態屬性 Current(OperationContext.Current)得到當前的OperationContext。借助 OperationContext,我們可以在服務端或者客戶端獲取或設置一些上下文,比如在客戶端可以通過它為出棧消息(outgoing message)添加SOAP報頭,以及HTTP報頭(比如Cookie)等。在服務端,則可以通過OperationContex獲取在客戶端設置的 SOAP報頭和HTTP報頭。關于OperationContext的詳細信息,可以參閱MSDN在線文檔。
步驟三:服務寄宿
我們通過一個控制臺應用程序完成對BookService的寄宿工作,并將所有的服務寄宿的參數定義在代碼中。由于雙工通信依賴于一個雙工的信道棧,即依賴于一個能夠支持雙工通信的綁定,在此我們選用了WSDualHttpBinding。
using System; using System.Collections.Generic; using System.Linq; using System.ServiceModel; using System.ServiceModel.Description; using System.Text; using System.Threading.Tasks; using WcfServiceLib; namespace ConsoleHosting { class Program { static void Main(string[] args) { //創建宿主的基地址 Uri baseAddress = new Uri("http://localhost:8080/BookService"); //創建宿主 using (ServiceHost host = new ServiceHost(typeof(BookService), baseAddress)) { //向宿主中添加終結點 host.AddServiceEndpoint(typeof(IBookService), new WSDualHttpBinding (), baseAddress); if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null) { //將HttpGetEnabled屬性設置為true ServiceMetadataBehavior behavior = new ServiceMetadataBehavior(); behavior.HttpGetEnabled = true; behavior.HttpGetUrl = baseAddress; //將行為添加到Behaviors中 host.Description.Behaviors.Add(behavior); //打開宿主 host.Opened += delegate { Console.WriteLine("BookService控制臺程序寄宿已經啟動,HTTP監聽已啟動....,按任意鍵終止服務!"); }; host.Open(); //print endpoint information Console.ForegroundColor = ConsoleColor.Yellow; foreach (ServiceEndpoint se in host.Description.Endpoints) { Console.WriteLine("[終結點]: {0}\r\n\t[A-地址]: {1} \r\n\t [B-綁定]: {2} \r\n\t [C-協定]: {3}", se.Name, se.Address, se.Binding.Name, se.Contract.Name); } Console.Read(); } } } } }
注: 在WCF預定義綁定類型中,WSDualHttpBinding和NetTcpBinding均提供了對雙工通信的支持,但是兩者在對雙工通信的實現機制上卻有本質的區別。WSDualHttpBinding是基于HTTP傳輸協議的;而HTTP協議本身是基于請求-回復的傳輸協議,基于HTTP的通道本質上都是單向的。WSDualHttpBinding實際上創建了兩個通道,一個用于客戶端向服務端的通信,而另一個則用于服務端到客戶端的通信,從而間接地提供了雙工通信的實現。而NetTcpBinding完全基于支持雙工通信的TCP協議。
步驟四:實現回調契約
在客戶端程序為回調契約提供實現,在下面的代碼中BookCallBack實現了回調契約ICallback,在DisplayResult方法中對書籍名稱與日期的顯示的輸出。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WinClient { class BookCallBack : BookServiceReference.IBookServiceCallback { //聲明一個delegate(委托)類型:delegateDispalyResult,該類型可以搭載返回值為空,參數只有一個(string型)的方法。 public delegate void delegateDispalyResult(string result); //聲明一個delegateDispalyResult類型的對象。該對象代表了返回值為空,參數只有一個(string型)的方法。它可以搭載N個方法。 public delegateDispalyResult mainThread; public void DisplayResult(string result) { mainThread(result);//通過委托的方法在主界面中顯示書籍名稱與日期信息 Console.WriteLine( result); } } }
步驟五:服務調用
接下來實現對雙工服務的調用,這是一個通過Web 服務引用,添加相應的WCF服務調用對象(關于如何添加Web服務引用請參考前面的文章)。在服務調用程序中,先創建回調對象,并通過InstanceContext對回調對象進行包裝,然后把InstanceContext對象做為參數傳遞給服務調用對象。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.ServiceModel; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace WinClient { public partial class Form1 : Form { BookCallBack call; public Form1() { InitializeComponent(); //創建BookCallBack類的對象 BookCallBack call = new BookCallBack(); call.mainThread += new BookCallBack.delegateDispalyResult(DisplayResult); instanceContext = new InstanceContext(call); } InstanceContext instanceContext; private void buttonTwoWay_Click(object sender, EventArgs e) { BookServiceReference.BookServiceClient client = new BookServiceReference.BookServiceClient(instanceContext); //在BookCallBack對象的mainThread(委托)對象上搭載兩個方法,在線程中調用mainThread對象時相當于調用了這兩個方法。 textBox1.Text += string.Format("開始調用wcf服務:{0}\r\n\r\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); client.DisplayName("科學可以這樣看叢書"); textBox1.Text += string.Format("\r\n\r\n調用結束:{0}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); } /// <summary> /// 在界面上顯示回調結果 /// </summary> /// <param name="result"></param> private void DisplayResult(string result) { //判斷該方法是否被主線程調用,也就是創建labMessage1控件的線程,當控件的InvokeRequired屬性為ture時,說明是被主線程以外的線程調用。如果不加判斷,會造成異常 if (this.textBox1.InvokeRequired) { //再次創建一個BookCallBack類的對象 call = new BookCallBack(); //為新對象的mainThread對象搭載方法 call.mainThread += new BookCallBack.delegateDispalyResult(DisplayResult); //this指窗體,在這調用窗體的Invoke方法,也就是用窗體的創建線程來執行mainThread對象委托的方法,再加上需要的參數(i) this.Invoke(call.mainThread, new object[] { result }); } else { textBox1.Text +="\r\n"+ result; } } } }
在服務寄宿程序啟用的情況下,運行客戶端程序后,通過服務端執行的運算結果會通過回調客戶端的操作顯示出來,下面是最終輸出的結果。
文章列表
留言列表