[老老實實學WCF] 第四篇 初探通信--ChannelFactory


老老實實學WCF

第四篇 初探通信--ChannelFactory

 

通過前幾篇的學習,我們簡單了解了WCF的服務端-客戶端模型,可以建立一個簡單的WCF通信程序,並且可以把我們的服務寄宿在IIS中了。我們不 禁感嘆WCF模型的簡單,寥寥數行代碼和配置,就可以把通信建立起來。然而,仔細品味一下,這里面仍有許多疑點:服務器是如何建起服務的?我們在客戶端調 用一個操作后發生了什么?元數據到底是什么東西?等等。我們現在對WCF的理解應該還處於初級階段,我們就會覺得有許多這樣的謎團了。

 

雖然我們生活在WCF為我們構建的美好的應用層空間中,但是對於任何一項技術,我們都應力求做到知其所以然,對於底層知識的了解有助於我們更好的理解上層應用,因此在剛開始學習入門的時候,慢一點、細一點,我覺得是很有好處的。

 

言歸正傳,我們現在已經知道了一件最基本的事情,客戶端和服務器是要進行通信的。那么這個通信是如何發生的呢?根據我們前面的學習,從實際操作上 看,我們在服務端定義好協定和實現,配置好公開的終結點,打開元數據交換,在客戶端添加服務引用,然后就直接new出來一個叫做XXXClient 的對象,這個對象擁有服務協定里的所有方法,直接調用就可以了。仔細想想?天哪,這一切是怎么發生的?!

 

服務端定義協定和實現並公開終結點,這看上去沒什么問題,雖然我們對底層的實現不了解,但總歸是合乎邏輯的,而客戶端怎么就通過一個添加服務引用就搞定一切了呢?似乎秘密在這個添加的服務引用中。

 

打開第二篇中我們建立的客戶端(如果你為第三篇的IIS服務建立了客戶端,打開這個也行,我用的就是這個),看看服務引用里面有什么。

1. 服務引用初探

在解決方案瀏覽器中點擊上方的"顯示所有文件"按鈕,然后展開服務引用。

這么一大堆,有一些xsd文件我們可能知道是框架描述的文檔,那wsdl什么的是什么,還有disco(迪斯科?)是什么,一頭霧水。

其中有一個cs文件,這個想必我們應該看得懂,打開來看看

[csharp] view plain copy
 
  1. //------------------------------------------------------------------------------  
  2. // <auto-generated>  
  3. //     此代碼由工具生成。  
  4. //     運行時版本:4.0.30319.261  
  5. //  
  6. //     對此文件的更改可能會導致不正確的行為,並且如果  
  7. //     重新生成代碼,這些更改將會丟失。  
  8. // </auto-generated>  
  9. //------------------------------------------------------------------------------  
  10.   
  11. namespace ConsoleClient.LearnWCF {  
  12.       
  13.       
  14.     [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]  
  15.     [System.ServiceModel.ServiceContractAttribute(ConfigurationName="LearnWCF.IHelloWCF")]  
  16.     public interface IHelloWCF {  
  17.           
  18.         [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IHelloWCF/HelloWCF", ReplyAction="http://tempuri.org/IHelloWCF/HelloWCFResponse")]  
  19.         string HelloWCF();  
  20.     }  
  21.       
  22.     [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]  
  23.     public interface IHelloWCFChannel : ConsoleClient.LearnWCF.IHelloWCF, System.ServiceModel.IClientChannel {  
  24.     }  
  25.       
  26.     [System.Diagnostics.DebuggerStepThroughAttribute()]  
  27.     [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]  
  28.     public partial class HelloWCFClient : System.ServiceModel.ClientBase<ConsoleClient.LearnWCF.IHelloWCF>, ConsoleClient.LearnWCF.IHelloWCF {  
  29.           
  30.         public HelloWCFClient() {  
  31.         }  
  32.           
  33.         public HelloWCFClient(string endpointConfigurationName) :   
  34.                 base(endpointConfigurationName) {  
  35.         }  
  36.           
  37.         public HelloWCFClient(string endpointConfigurationName, string remoteAddress) :   
  38.                 base(endpointConfigurationName, remoteAddress) {  
  39.         }  
  40.           
  41.         public HelloWCFClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :   
  42.                 base(endpointConfigurationName, remoteAddress) {  
  43.         }  
  44.           
  45.         public HelloWCFClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :   
  46.                 base(binding, remoteAddress) {  
  47.         }  
  48.           
  49.         public string HelloWCF() {  
  50.             return base.Channel.HelloWCF();  
  51.         }  
  52.     }  
  53. }  

這么一堆代碼,都不是我們寫的,初步看似乎有幾個類/接口,IHelloWCF,這個應該是服務協定,估計可能是從服務端下載來 的,HelloWCFClient,這個就是我們的客戶端代理嘛,我們在前面用過,原來是在這里定義的,可是后面繼承的 ClientBase<>是干嘛用的,還重載了這么多的構造函數。還有一個IHelloWCFChannel接口,我們找遍解決方案也找不到 什么地方用到他了啊,干嘛在這里定義出來呢?

先不去細想這些代碼的具體意義,看到這里,我們在對VS2010由衷贊嘆的同時,不由得心中升起一絲憂慮,如果沒有了VS2010,沒有了IDE,沒有了"添加服務引用",我們該怎么辦?

2. 我們自己寫通信

雖然我們料想VS2010也不會消失,我們是可以一直享受它提供給我們的便利的。但是我們今天在這里研究,不妨把控制級別向下探一個層次。看看下面有什么。

 

通信到底是怎么發生的?簡單說就是兩個終結點一個通道,實際上客戶端也是有一個終結點的,客戶端會在這兩個終結點之間建立一個 通道,然后把對服務端服務的調用封裝成消息沿通道送出,服務器端獲得消息后在服務器端建立服務對象,然后執行操作,將返回值再封裝成消息發給客戶端。

過程大概是這樣的,有些地方可能不太嚴謹,但是這個邏輯我們是可以理解的。如此看來,通訊的工作主要部分都在客戶端這邊,他要建立通道、發送消息,服務端基本上在等待請求。

 

客戶端需要哪些東西才能完成這一系列的操作呢?元數據和一些服務的類。服務類由System.ServiceModel類庫提 供了,只差元數據。提到元數據我們不禁倒吸一口涼氣,難道是那一堆XSD OOXX的東西么?我覺得,是也不是。就我們這個例子來說,元數據包括:服務協定、服務端終結點地址和綁定。對,就這么多。我們是不是一定要通過元數據交 換下載去服務端獲取元數據呢,當然不是,就這個例子來說,服務端是我們設計的,這三方面的元數據我們當然是了然於胸的。

 

所以,讓服務引用見鬼去吧,我們自己來。

 

(1) 建立客戶端。

這個過程我們很熟悉,建立一個控制台應用程序,不做任何其他事,僅有清清爽爽的program.cs

 

(2) 添加必要的引用

前面說過,客戶端完成通信的發起需要一些服務類的支持,這些類都定義在System.ServiceModel中,因此我們要添加這個程序集的引用。(注意是添加引用,不是服務引用)。

 

然后在Program.cs中using這個命名空間

[csharp] view plain copy
 
  1. using System.ServiceModel;  


(2) 編寫服務協定

服務協定是元數據中最重要的部分(還可能有數據協定等),協定接口是服務器和客戶端共同持有的,客戶端依靠協定來創建通道,然后在通道上調用協定的方法,方法的實現,客戶端是不知道的。客戶端只知道方法簽名和返回值(即接口)。

我們把在服務端定義的服務協定原封不動的照搬過來,注意,只把接口搬過來,不要把實現也搬過來,那是服務端才能擁有的。

服務協定我們都很熟悉了,背着打出來吧。就寫在Program類的后面

[csharp] view plain copy
 
  1. [ServiceContract]  
  2. public interface IHelloWCF  
  3. {  
  4.     [OperationContract]  
  5.     string HelloWCF();  
  6. }  

OK,元數據的第一部分完成了,另外兩部分我們在代碼里面提供。

 

(3) 通道工廠登場

System.ServiceModel提供了一個名為ChannelFactory<>的類,他接受服務協定接 口作為泛型參數,這樣new出來的實例叫做服務協定XXX的通道工廠。顧名思義了,這個工廠專門生產通道,這個通道就是架設在服務器終結點和客戶端終結點 之間的通信通道了。由於這個通道是用服務協定來建立的,所以就可以在這個通道上調用這個服務協定的操作了。

這個通道工廠類的構造函數接受一些重載參數,使用這些參數向通道工廠提供服務端終結點的信息,包括地址和綁定,這正是元數據的其他兩部分。我們先把這兩樣做好預備着。

 

地址,也可以稱終結點地址,實際上就是個URI了,我們也有一個專用的服務類來表示他,叫做EndpointAddress,我們new一個它的實例:

[csharp] view plain copy
 
  1. EndpointAddress address = new EndpointAddress("http://localhost/IISService/HelloWCFService.svc");  

只接受一個String參數,就是URI地址,這里我用了第三篇中建立的IIS服務的地址。

 

綁定,我們的服務端終結點要求的是wsHttpBinding,我們也可以用服務類來表示,叫做WSHttpBinding,我們new一個它的實例:

[csharp] view plain copy
 
  1. WSHttpBinding binding = new WSHttpBinding();  

使用參數為空的構造函數就可以。

 

好的,元數據的其他兩樣也准備齊全,現在請通道工廠閃亮登場,我們new一個它的實例,用剛才建立的服務協定接口作為泛型參數,使用上面建立的地址和綁定對象作為構造函數的參數:

[csharp] view plain copy
 
  1. ChannelFactory<IHelloWCF> factory = new ChannelFactory<IHelloWCF>(binding, address);  


有了工廠,接下來就要開始生產通道,通過執行通道工廠的CreateChannel方法來生產一個通道,由於工廠是用我們的協定接口創建的,所生產的通道也是實現這個接口的,我們可以直接用協定接口來聲明其返回值。

[csharp] view plain copy
 
  1. IHelloWCF channel = factory.CreateChannel();  


現在,通道已經打開,我們可以調用這個通道上的協定方法了。

[csharp] view plain copy
 
  1. string result = channel.HelloWCF();  


然后我們把結果輸出,就像之前做的客戶端程序一樣。

[csharp] view plain copy
 
  1. Console.WriteLine(result);  
  2. Console.ReadLine();  

F5運行一下,看結果

 

Yahoo!,我們沒有使用元數交換的功能,憑着手繪的元數據和代碼就完成了客戶端到服務器端的通信。沒有服務引用、沒有配置文件,我們依然做得到。雖然對整個過程還不能完全明了,但是對通信過程已經有些理解了。

Program.cs的全部代碼

[csharp] view plain copy
 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.ServiceModel;  
  6.   
  7. namespace ConsoleClient  
  8. {  
  9.     class Program  
  10.     {  
  11.         static void Main(string[] args)  
  12.         {  
  13.             EndpointAddress address = new EndpointAddress("http://localhost/IISService/HelloWCFService.svc");  
  14.             WSHttpBinding binding = new WSHttpBinding();  
  15.   
  16.             ChannelFactory<IHelloWCF> factory = new ChannelFactory<IHelloWCF>(binding, address);  
  17.   
  18.             IHelloWCF channel = factory.CreateChannel();  
  19.   
  20.             string result = channel.HelloWCF();  
  21.   
  22.             Console.WriteLine(result);  
  23.             Console.ReadLine();  
  24.         }  
  25.     }  
  26.   
  27.     [ServiceContract]  
  28.     public interface IHelloWCF  
  29.     {  
  30.         [OperationContract]  
  31.         string HelloWCF();  
  32.     }  
  33. }  


 

4. 再展開一點點

到這里已經很成功了,我們再稍微展開一些,還記得我們稍早前在服務引用生成的文件reference.cs看到的一個接口IHelloWCFChannel么?我們找遍解決方案也沒發現哪個地方用到它?他是不是沒用的東西呢,當然不會,我們現在來研究一下它。

 

我們上面手繪的程序可以打開通道並調用服務,然而我們回憶我們之前通過服務引用建立的客戶端都是提供一個Close()方法來關閉服務連接的。使用我們這種方法按說也應該關閉通道才對,雖然客戶端關閉通道就會被關閉了,但是在使用完通道后關閉之總是好的習慣。

 

可是,如何實現呢?我們發現通道無法提供關閉的方法,這是因為我們用IHelloWCF接口聲明的通道對象,那這個對象自然只 能提供接口所規定的方法了。而實際上通道對象本身是提供關閉方法,只是被我們顯示的接口聲明給屏蔽了,通道其實已經實現了另一個接口叫做 IClientChannel,這個接口提供了打開和關閉通道的方法。如果我們要調用,只需要把通道對象強制轉換成IClientChannel接口類型 就可以了:

[csharp] view plain copy
 
  1. ((IClientChannel)channel).Close();  


但是這么做不夠自然優雅,強制轉換總是讓人莫名煩惱的東西。能不能保持IHelloWCF的對象類型並讓他可以提供關閉方法呢?當然是可以的。我們再建立 一個接口,讓這個接口同時實現IHelloWCF服務協定接口和IClientChannel接口,並用這個新接口去new 通道工廠,這樣生產出來的通道對象不就可以同時使用IHelloWCF中的服務操作和IClientChannel中的關閉通道的方法了么?

 

首先,做這個新接口,這個接口只是把服務協定接口和IClientChannel拼接,本身沒有其他成員,是一個空接口。

[csharp] view plain copy
 
  1. public interface IHelloWCFChannel : IHelloWCF, IClientChannel  
  2. {   
  3.   
  4. }  


然后,修改一下前面的建立通道工廠的語句,用這個新接口名作為泛型參數來new通道工廠

[csharp] view plain copy
 
  1. ChannelFactory<IHelloWCFChannel> factory = new ChannelFactory<IHelloWCFChannel>(binding, address);  

 

修改一下生產通道方法的對象聲明類型,用新接口類型聲明:

[csharp] view plain copy
 
  1. IHelloWCFChannel channel = factory.CreateChannel();  


最后,我們在調用服務操作之后,就可以直接調用關閉通道的方法了:

[csharp] view plain copy
 
  1. channel.Close();  


修改后的program.cs源代碼:

[csharp] view plain copy
 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.ServiceModel;  
  6.   
  7. namespace ConsoleClient  
  8. {  
  9.     class Program  
  10.     {  
  11.         static void Main(string[] args)  
  12.         {  
  13.             EndpointAddress address = new EndpointAddress("http://localhost/IISService/HelloWCFService.svc");  
  14.             WSHttpBinding binding = new WSHttpBinding();  
  15.   
  16.             ChannelFactory<IHelloWCFChannel> factory = new ChannelFactory<IHelloWCFChannel>(binding, address);  
  17.   
  18.             IHelloWCFChannel channel = factory.CreateChannel();  
  19.   
  20.             string result = channel.HelloWCF();  
  21.   
  22.             channel.Close();  
  23.   
  24.             Console.WriteLine(result);  
  25.             Console.ReadLine();  
  26.         }  
  27.     }  
  28.   
  29.     [ServiceContract]  
  30.     public interface IHelloWCF  
  31.     {  
  32.         [OperationContract]  
  33.         string HelloWCF();  
  34.     }  
  35.   
  36.     public interface IHelloWCFChannel : IHelloWCF, IClientChannel  
  37.     {   
  38.       
  39.     }  
  40.       
  41. }  


現在,我們明白了服務引用中那個看上去沒用的接口是什么作用了吧。然而服務引用中的實現跟我們這種還是有區別的,它使用了一個叫做ClientBase<>的類來進行通道通信,我們后面會展開。

5. 總結

今天的研究稍微深入了一點點,沒有完全理解也沒有關系,心里有個概念就可以了。

我們通過手繪代碼的方法實現了客戶端和服務端的通信,沒有借助元數據交換工具。這讓我們對服務端和客戶端通信有了更接近真相的 一層認識。其實所謂的元數據交換就是讓我們獲得這樣甚至更簡單的編程模型,它背后做的事情跟我們今天做的是很類似的,想像一下,產品級的服務可能有許多的 協定接口,許多的終結點,我們不可能也不應該耗費力氣把他們在客戶端中再手動提供一次,因此元數據交換是很有意義的,我們應該學會使用它,我們還要在后面 的學習中不斷掌握駕馭元數據交換工具的辦法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM