WCF是DotNet體系中很重要的一項技術,可是組內不少組員經過書籍自學的時候 感受涉及面太廣、配置文件太複雜,新名詞太多、抓不到頭緒,有感於此,決定進行一次組內技術培訓,順便把培訓講義整理到blog上來。不求大而全,而是要 讓初學者快速入門,所以想入實例入手,並刻意隱藏一些初期用不到的內容,以下降入門門檻。有任何錯誤歡迎指正。sql
注:本系列文章基於.Net Framework 3.5,以教程的最後會概括一下到了4.0中有哪些差別。瀏覽器
----------------------- 分隔線 -----------------------安全
第一篇:入門,構建第一個WCF程序網絡
一、服務端多線程
創建一個控制檯應用程序做爲Server,新建一個接口IData做爲服務契約。這個契約接口一下子也要放到Client端,這樣雙方纔能遵循相同的標準。別忘了添加對System.ServiceModel的引用。併發
using System;app
using System.ServiceModel;異步
using System.Text;tcp
namespace Serveride
{
/// <summary>
/// 用ServiceContract來標記此接口是WCF的服務契約,能夠像WebService同樣指定一個Namespace,若是不指定,就是默認的http://tempuri.org
/// </summary>
[ServiceContract(Namespace="WCF.Demo")]
public interface IData
{
/// <summary>
/// 用OperationContract來標記此方法是操做契約
/// </summary>
[OperationContract]
string SayHello(string userName);
}
}
針對這個接口創建實現類,這個類纔是真正幹活的,工做在服務端,不出如今客戶端:
using System;
using System.Text;
namespace Server
{
/// <summary>
/// 實現IData接口,此處不須要寫契約標記
/// </summary>
public class DataProvider : IData
{
public string SayHello(string userName)
{
return string.Format("Hello {0}.", userName);
}
}
}
爲工程添加一個App.config文件,這裏面要定義與服務發佈相關的參數。WCF中常見的作法是用代碼寫服務邏輯,可是用配置文件來定義服務發佈方式,這樣作的好處是鬆散耦合。
<?xmlversion="1.0"encoding="utf-8"?>
<configuration>
<system.serviceModel>
<!-- 看到services節,就代表這是在定義服務相關的內容 -->
<services>
<!-- 定義一個服務,name是契約實現類的全名 -->
<servicename="Server.DataProvider">
<!-- 既然要對外提供服務,就要有服務地址,此處定義爲 http://localhost:8080/wcf,須要注意,地址老是帶着類型標頭的 -->
<host>
<baseAddresses>
<addbaseAddress="http://localhost:8080/wcf"/>
</baseAddresses>
</host>
<!-- 定義一下終節點,address通常爲空,若是不爲空,最終服務地址就是在baseAddress的基礎上加上這個address,binding指定爲basicHttpBinding,這是最基礎的基於http的綁定方式,contract標明這是爲哪一個契約服務 -->
<endpointaddress=""binding="basicHttpBinding"contract="Server.IData"/>
</service>
</services>
</system.serviceModel>
</configuration>
萬事具有,只剩最後一步了,將服務發佈出去:
using System;
using System.ServiceModel;
namespace Server
{
class Program
{
static void Main(string[] args)
{
//定義一個ServiceHost,注意參數中要使用契約實現類而不是接口
using(ServiceHost host = new ServiceHost(typeof(Server.DataProvider)))
{
host.Open();
Console.WriteLine("Service Running ...");
Console.ReadKey();
host.Close();
}
}
}
}
有人可能會問服務發佈到哪去了?沒指定地址呀?這是一個初學者容易搞不明白的地方。
是 的,此時App.config中的定義就發揮做用了,因爲ServiceHost中指定對Server.DataProvider類服務,而 App.config中定義了name="Server.DataProvider"的service,其下有endpoint,定義了綁定方式是 basicHttpBinding,而http方式的baseAddress只有一個,就是 http://localhost:8080/wcf。
編譯運行,屏幕顯示Service Running ... 就是正常跑起來了,此時若是用命令行 netstat -ano | findstr "8080" 看一下,應該有以下輸出:
TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 4
TCP [::]:8080 [::]:0 LISTENING 4
表示咱們的程序已經在TCP 8080端口開始監聽了。值得注意的是PID是4,這是系統進程而不是咱們本身的進程,這說明WCF程序對外提供HTTP服務時,是借用了系統功能(http.sys)。
此 時若是咱們用瀏覽器訪問一下 http://localhost:8080/wcf,不報錯,可是會提示「當前已禁用此服務的元數據發佈」,這是因爲默認不容許以http get方式獲取服務的WSDL,咱們不用管它,不影響後面的使用,之後的章節中咱們再來看這個問題。
二、客戶端
再創建一個控制檯應用程序做爲Client,把Server中的接口IData拷過來,由於這是服務契約。
爲工程添加一個App.config文件,這裏面要定義客戶端訪問的相關參數,這裏我去掉了一些用不上的參數,以保持配置文件簡單,防止各位看暈了頭。
<?xmlversion="1.0"encoding="utf-8"?>
<configuration>
<system.serviceModel>
<!-- 看到client,就代表是客戶端設置 -->
<client>
<!-- 定義訪問時的終節點,name也是隨意取的,注意address是Server端發佈時指定的baseAddress+endpoint的address,binding也要對應,contract就更不用說了,因爲以前把IData.cs拷過來的時候沒有修改命名空間,因此仍是Server.IData -->
<endpointname="DataService"address="http://localhost:8080/wcf"binding="basicHttpBinding"contract="Server.IData"/>
</client>
</system.serviceModel>
</configuration>
而後寫代碼,來調用Server端發佈的SayHello方法:
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
namespace Client
{
class Program
{
static void Main(string[] args)
{
//客戶端訪問有多種方式,此處只顯示一種
//利用ChannelFactory的CreateChannel方法建立一個IData的代理對象,其中參數「DataService」就是剛纔在App.config中定義的endpoint的名稱
var proxy = new ChannelFactory<Server.IData>("DataService").CreateChannel();
//調用SayHello方法
Console.WriteLine(proxy.SayHello("WCF"));
//用完後必定要關閉,由於服務端有最大鏈接數,不關閉會在必定時間內一直佔着有效鏈接
((IChannel)proxy).Close();
}
}
}
編譯運行,屏幕應能正常打印出「Hello WCF.」。第一個入門demo就搞定了,應該仍是比較簡單的。只是App.config的配置有些複雜,後面咱們會看到,其實也能夠不要配置,直接用代碼搞定,不過從鬆散耦合的角度講不建議這麼作。
第二篇:聊聊binding
上一篇構建的WCF程序,binding指定的是basicHttpBinding,這是最基礎的通信方式,基於http,不加密,抓一下包的話,應該是這樣的:
就是SOAP,和WebService是同樣的。 basicHttpBinding的優點在於通用,又基於http,因此在跨語言的開發中或者是穿透複雜網絡環境時佔優點,可是效率比較低,由於SOAP 的序列化和反序列化比較耗時,傳輸的數據量也比較大,並且是明文,安全性差。
WCF的binding有不少種,包括:
basicHttpBinding(經常使用) | 符合WSBasicProfile 1.1標準,以提供最大的互操做性 |
wsHttpBinding(經常使用) | 符合WS-*協議的HTTP通信,加密 |
wsDualHttpBinding | 雙工http通訊,初始信息的接收者不會直接響應發送者,而是能夠在必定時間以內傳送任意次的響應 |
wsFederationBinding | http通訊,對服務資源的訪問能夠根據一個顯式肯定的憑據提供程序發出的憑據加以控制 |
netTcpBinding(經常使用) | 提供網絡裏WCF軟件實體之間的安全可靠高性能的通訊 |
netNamedPipeBinding | 同一臺機器上的WCF軟件實體之間的安全可靠高性能的通訊 |
netMsmqBinding | WCF軟件實體與其它軟件實體間經過MSMQ通訊 |
msmqIntegrationBinding | 軟件實體與其它軟件實體間經過MSMQ通訊 |
netPeerTcpBinding | WCF軟件實體間經過Windows對等網絡技術通訊 |
內容不少,咱們挑wsHttpBinding和netTcpBinding繼續研究一下,這兩個最有表明性。在上一篇demo的基礎上修改一下。
一、服務端
服務端的功能邏輯沒有任何變化,只是調整綁定方式,因此只調整App.config:
與以前相比,增長了一個baseAddress和一個endpoint,另外調 整了以前endpoint的綁定方式。如今針對同一個服務契約,有兩個endpoint了,可是它們不會衝突,由於二者的網絡協議不一樣,因此 wsHttpBinding的會使用http://localhost:8080/wcf的地址,而netTcpBinding的會使用net.tcp: //localhost:8081/wcf的地址。
若是同時定義了basicHttpBinding和wsHttpBinding呢?那麼它們的address必定不能相同,由於baseAddress已經相同了,adress還同樣,就沒法訪問了。
編譯運行一下,而後命令行「netstat -ano」看一下,應該能看到8080和8081都開始監聽了。
TCP 8081端口的PID顯示是咱們的服務程序在對外監聽。
二、客戶端
因爲服務端修改了綁定方式,客戶端必需要與之匹配,先修改App.config文件:
須要指出的是,服務端開放了2種訪問方式,客戶端不必定也要寫2個endpoint,這裏是爲了測試兩種綁定。
調用代碼也要作個小修改:
編譯運行一下,應該可以輸出兩行 Hello WCF.。
抓包看看:
wsHttpBinding方式的客戶端與服務端總共交換了60多個數據包,這是由於雙方要先握手交換密鑰,另外因爲數據加了密,長度也變大了。這裏只截第一次交互的數據看看:
應該是雙方交換了一個256位的密鑰,反正全部的數據交互都再也不是明文的了。
再來看netTcpBinding的此次,仍是隻截一小段吧:
從效率上講,tcp方式要比http方式高得多,同時http方式,basicHttpBinding又要比wsHttpBinding高得多,在實際使用中,你們要看需求來決定使用哪一種方式。
OK,下一篇咱們看看能不能不用寫複雜的配置就發佈WCF服務,也以此來加深一下對Host、Contract、Binding、BaseAddress的理解。
通常狀況下調用遠程WebService經過代理類直接訪問就能夠,但是若是WebService是在https站點下,調用時就要分狀況考慮了,整理了一下:
一、客戶端證書已正確安裝
指已經在客戶端安裝了客戶端證書到證書存儲區,且證書符合如下幾個條件:
◆ 證書中定義的使用者與訪問WebService時使用的域名一致;
◆ 證書未過時;
◆ 證書鏈在本機完整可信;
關於證書鏈完整可信,是指本證書,以及向上追溯的各級頒發者,直至根證書頒發者,都被系統認可。
此時直接調用便可,與訪問http時沒有區別,底層會自動處理SSL握手。若是有任何一項不符合要求,調用時就會產生「基礎鏈接已經關閉:未能爲 SSL/TLS 安全通道創建信任關係」的異常,此時要使用2中的方法。
二、客戶端證書未正確安裝
關鍵在於要讓證書驗證時忽略全部錯誤。不用擔憂,忽略錯誤只是指不用判斷證書有效性,並不會影響通訊信道的加密過程。
第三篇:試着去掉配置文件
經過配置文件來設置Host、Endpoint、Binding等是WCF中推 薦的方法,這樣可使發佈儘可能靈活。其實配置文件中的值,最終仍是要體現到代碼中的,只不過這部分工做由底層幫你作了。咱們今天來嘗試去掉配置文件,用純 代碼實現發佈過程,同時加深一下對層次關係的理解。
一、服務端
在上回的基礎上刪掉App.config吧,而後把Main方法修改一下:
若是咱們把代碼和以前的App.config對比着的地一下,就會發現元素是對應的。咱們來整理一下目前爲止出現的層級關係:
ServiceHost |
二、客戶端
一樣能夠刪掉App.config了,代碼改一下:
對照着上面,也來比對一下代碼中現出的對象與App.config中的定義:
ClientEndpoint 客戶端終結點,對應config中的<endpoint> ├ ServiceContract 服務契約,對應config中<endpoint>的contract屬性 ├ Binding 綁定,對應config中<endpoint>的binding屬性 └ EndpointAddress 地址,對應config中<endpoint>的address屬性 |
通常狀況下,仍是建議利用App.config來作發佈的相關設定,不要寫死代碼。但若是隻能在程序運行時動態獲取發佈的相關參數,那App.config就不行了。
OK,又前進了一點,下一篇會看看如何傳遞複雜對象。
第四篇:用數據契約傳遞自定義數據對象
以前的演示中,咱們一直都是在用string類型作參數和返回值,實際項目中確定會傳遞自定義的數據類型。與WebService不一樣,WCF想傳遞自定義數據,必需要將其定義爲數據契約。看一個例子:
這個契約須要在客戶端和服務端都存在,而後它就能夠做爲參數或返回值,在雙方互相傳遞了,具體例子就省略了。
這裏面有一點須要注意,數據契約與服務契約有一點小小的區別,數據契約要求在客戶端和服務端必須保持徹底一致的類名與命名空間,不然就沒法傳遞數據,這與服務契約是不一樣的,服務契約放到客戶端時容許換個命名空間。
組內有同事遇到過相似的問題,直接把數據契約類copy了一份到客戶端以後改了命名空間,而後就一直取不到數據。 這裏也引出另外一個話題,推薦把各類契約(不含實現類)單獨封裝成一個dll,雙方均引用它,結構上清晰,也避免出錯。
第五篇:用IIS作Host
以前幾篇的Demo中,咱們一直在用控制檯程序作Server,今天換IIS來作Host,在Web Application中添加WCF的服務。
其 實在Web Application中添加WCF服務是最簡單的,「新建項」中有專用的「WCF服務」,擴展名爲svc。好比咱們建立 DataService.svc,Visual Studio會本身建立好DataService.svc、DataService.svc.cs、IDataService.cs共三個文件,而且自動 在Web.config中增長默認設置。
從功能上看,IDataService.cs是服務契約,DataService.svc.cs是契約的實現類,DataService.svc沒什麼實際用處,裏面也只有一行代碼:
<%@ ServiceHost Language="C#" Debug="true" Service="WebServer.DataService" CodeBehind="DataService.svc.cs" %>
Web.config中的部分與以前略有不一樣,咱們來分析一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<
system.serviceModel
>
<!-- 這個節點是新加的,後面會討論一下 -->
<
behaviors
>
<
serviceBehaviors
>
<
behavior
name
=
"WebServer.DataServiceBehavior"
>
<
serviceMetadata
httpGetEnabled
=
"true"
/>
<
serviceDebug
includeExceptionDetailInFaults
=
"false"
/>
</
behavior
>
</
serviceBehaviors
>
</
behaviors
>
<
services
>
<!-- 新增了behaviorConfiguration屬性,值就是上面定義過的behavior的name,表示此service使用指定的behavior配置 -->
<
service
behaviorConfiguration
=
"WebServer.DataServiceBehavior"
name
=
"WebServer.DataService"
>
<
endpoint
address
=
""
binding
=
"wsHttpBinding"
contract
=
"WebServer.IDataService"
/>
<!-- 這個endpoint是新加的,後面會討論一下 -->
<
endpoint
address
=
"mex"
binding
=
"mexHttpBinding"
contract
=
"IMetadataExchange"
/>
</
service
>
</
services
>
</
system.serviceModel
>
|
與以前的App.config相比,有如下幾點不一樣:
一、<host>節點沒有了
host節點沒有了,對應的baseAddress也沒有了,這是理所固然的事,由於不須要,訪問DataService.svc文件時的url自己就是一個地址了。
二、新增長了一個<behaviors>節點
此節點用於控制行爲,在服務端只有<serviceBehaviors>子節點,下面的httpGetEnabled="true"表示容許用http的get方式獲取服務的元數據信息。還記得第一篇中的例子嗎?咱們用瀏覽器訪問時,獲得一個「當前已禁用此服務的元數據發佈」的提示,就是由於不容許以http的get方式獲取服務元數據形成的,這個屬性就是開啓此功能。
順便提一下,用svcutil.exe生成客戶端代理的話,對http類型的binding,必需要開放get方式訪問元數據。
三、新增長了一個endpoint
這個endpoint比較特殊,它的binding是mexHttpBinding,服務契約是IMetadataExchange。這個endpoint是用於元數據發佈的,它的功能實際上和剛纔的httpGetEnabled="true"有些重複。
我 們能夠這樣理解,當開啓了httpGetEnabled時,用 http://...../DataService.svc?wsdl 就能夠訪問到元數據;若是沒開啓,但有這個endpoint,用 http://...../DataService.svc/mex 也能夠訪問到元數據;若是都沒有,那對不起,不容許你獲取元數據。(固然啦,若是你已經有契約了,不會影響調用的)
多加一句,對tcp類型的binding,有一個對應的mexTcpBinding用於獲取元數據,沒有定義它,svcutil.exe就不能生成tcp類binding的代理類。
像netTcpBinding,就會利用IIS的net.tcp類型綁定,端口是808。
第六篇:單向與雙向通信
項目開發中咱們時常會遇到須要異步調用的問題,有時忽略服務端的返回值,有時但願服務端在須要的時候回調,今天就來看看在WCF中如何實現。
先看不須要服務端返回值的單向調用,老規矩,直接上代碼,再解釋。
一、服務端
契約接口中增長一個Sleep方法:
對應的實現類中,咱們來實現這個方法:
App.config就再也不列出來了,裏面用的是一個netTcpBinding的endpoint。
二、客戶端
首先別忘了客戶端的契約要與服務端保持一致,App.config也不列出來了,裏面有對應的endpoint。主要是調用的代碼:
按咱們的設想,兩次SayHello調用之間應該沒有延遲,由於Sleep是異步的嘛,編譯運行一下,結果…… 中間卡住了5秒,這是爲何呢?
這其中涉及到一個併發模型的問題,默認狀況下,WCF以單線程模型對外提供服務,也就是說,只能一個一個處理請求,即便是一個OneWay的單向調用,也只能等它處理完後纔會接着處理後面的SayHello請求,因此會卡5秒。
併發模式有如下三種,MSDN上的介紹有點複雜,我給簡化一下:
Single:單線程調用,請求只能一個一個處理; Reentrant:可重入的單線程調用,本質還是單線程,處理回調時,回調請求會進入隊列尾部排隊; Multiple:多線程調用,請求是併發的響應的; |
調置服務併發模型是在契約的實現類上,咱們爲DataService類加一個Attribute:
這回再編譯運行一下,連續打出了2行 Hello WCF,中間再也不阻塞了。
如今咱們再來看看雙向通信的問題。雙向通信能夠基於HTTP、TCP、 Named Pipe、MSMQ,但要注意,basicHttpBinding和wsHttpBinding不行,要換用wsDualHttpBinding,它會創 建兩個鏈接來進行雙向通信。至於TCP,它自然就是雙向通信的。
一、服務端
服務契約要進行修改,增長關於回調的契約:
對應的契約實現類要修改一下:
二、客戶端
仍然提醒一下別忘了把新的服務契約更新到客戶端。客戶端的調用要調整一下:
編譯運行,屏幕先顯示一行「Hello WCF.」,過5秒後顯示「收到回調了:睡醒了」。
第七篇:併發模型與實例模型
在以往使用WebService時,針對每個請求,服務類老是併發響應的,而且對每一個請求都生成新的實例。在WCF中,狀況發生變化了,它容許服務發佈者自定義並組合併發模型與實例模型。
併發模型有三種:
ConcurrencyMode Single: 單線程模型,能夠理解爲,針對一個客戶端,只有一個線程負責響應; |
實例模型也有三種:
InstanceContextMode PerCall: 針對每次調用都生成新的服務實例; |
組合起來是什麼效果呢?咱們來用示例代碼驗證一下。
一、服務端
服務契約,具體解釋見實現類吧,只要注意一下Sleep方法定義成了IsOneWay=true:
契約實現類:
App.config不列了,用的是netTcpBinding。
二、客戶端:
別的不列了,只列一下調用代碼:
OK,開始驗證:
一、ConcurrencyMode.Single + InstanceContextMode.PerCall
執行結果以下:
首先,打印出的 Counter全是1,說明針對每次請求,服務端的契約實現類(DataProvider)都是新實例化的。其次,同一個線程的兩次GetCounter 請求相隔了2秒,說明針對一個客戶端的調用阻塞了。再次,三個線程幾乎同時完成調用,說明它們之間並未互相阻塞。
二、ConcurrencyMode.Single + InstanceContextMode.PerSession
執行結果以下:
與上面相比,區別在於同一個線程的Counter在第二次調用時變成2了,說明針對同一個客戶端的兩次調用使用的是同一個服務實例。
三、ConcurrencyMode.Single + InstanceContextMode.Single
執行結果以下:
與上面相比,區別在於Counter一直在增加,這說明在服務端自始至終只有一個服務實例,它來響應全部的會話全部的請求。
四、ConcurrencyMode.Reentrant + InstanceContextMode.PerCall
執行結果以下:
和1的區別在於兩次GetCounter調用之間沒有2秒的延遲,這是因爲Reentrant模式下,回調被放入隊列尾部再處理,不會阻塞後面的調用。而且針對同一客戶端的每一個請求都是不一樣的服務實例在處理,不會阻塞。
五、ConcurrencyMode.Reentrant + InstanceContextMode.PerSession
執行結果以下:
與上面相比,區別在於又有了2秒的阻塞,這是因爲針對一個客戶端的屢次請求,是 同一個服務實例在處理,雖然容許重入,但只有一個對象,執行順序是:第一次GetCounter->Sleep(不阻塞)->Sleep回調 ->第二次GetCounter,因此表現上仍是阻塞住了。
六、ConcurrencyMode.Reentrant + InstanceContextMode.Single
執行結果以下:
自始至終只有一個服務實例,執行順序應該是:線程0的 GetCounter->線程2的GetCounter->線程1的GetCounter->線程0的Sleep(不阻塞)-> 線程2的Sleep(不阻塞)->線程0的Sleep回調->線程1的Sleep(不阻塞)->線程0的 GetCounter->線程2的Sleep回調->線程1的Sleep回調->線程1的GetCounter->線程2的 GetCounter。挺暈的。
七、ConcurrencyMode.Multiple + InstanceContextMode.PerCall
執行結果以下:
屢次調用徹底是併發的,每次調用的實例也是新建立的。
八、ConcurrencyMode.Multiple + InstanceContextMode.PerSession
執行結果以下:
屢次調用徹底併發,但針對同一個會話,實例是相同的。
九、ConcurrencyMode.Multiple + InstanceContextMode.Single
執行結果以下:
徹底併發,Counter也一直增加,代表自始至終是同一個服務實例。