WCF簡單教程

WCF是DotNet體系中很重要的一項技術,可是組內不少組員經過書籍自學的時候 感受涉及面太廣、配置文件太複雜,新名詞太多、抓不到頭緒,有感於此,決定進行一次組內技術培訓,順便把培訓講義整理到blog上來。不求大而全,而是要 讓初學者快速入門,所以想入實例入手,並刻意隱藏一些初期用不到的內容,以下降入門門檻。有任何錯誤歡迎指正。sql

注:本系列文章基於.Net Framework 3.5,以教程的最後會概括一下到了4.0中有哪些差別。瀏覽器

----------------------- 分隔線 -----------------------安全

第一篇:入門,構建第一個WCF程序網絡

一、服務端多線程

創建一個控制檯應用程序做爲Server,新建一個接口IData做爲服務契約。這個契約接口一下子也要放到Client端,這樣雙方纔能遵循相同的標準。別忘了添加對System.ServiceModel的引用。併發

  1. using System;app

  2. using System.ServiceModel;異步

  3. using System.Text;tcp

  4. namespace Serveride

  5. {

  6.    /// <summary>

  7.    /// 用ServiceContract來標記此接口是WCF的服務契約,能夠像WebService同樣指定一個Namespace,若是不指定,就是默認的http://tempuri.org

  8.    /// </summary>

  9.    [ServiceContract(Namespace="WCF.Demo")]

  10.    public interface IData

  11.    {

  12.        /// <summary>

  13.        /// 用OperationContract來標記此方法是操做契約

  14.        /// </summary>

  15.        [OperationContract]

  16.        string SayHello(string userName);

  17.    }

  18. }


針對這個接口創建實現類,這個類纔是真正幹活的,工做在服務端,不出如今客戶端:

  1. using System;

  2. using System.Text;

  3. namespace Server

  4. {

  5.    /// <summary>

  6.    /// 實現IData接口,此處不須要寫契約標記

  7.    /// </summary>

  8.    public class DataProvider : IData

  9.    {

  10.        public string SayHello(string userName)

  11.        {

  12.            return string.Format("Hello {0}.", userName);

  13.        }

  14.    }

  15. }


爲工程添加一個App.config文件,這裏面要定義與服務發佈相關的參數。WCF中常見的作法是用代碼寫服務邏輯,可是用配置文件來定義服務發佈方式,這樣作的好處是鬆散耦合。

  1. <?xmlversion="1.0"encoding="utf-8"?>

  2. <configuration>

  3. <system.serviceModel>

  4.  <!-- 看到services節,就代表這是在定義服務相關的內容 -->

  5.  <services>

  6.  <!-- 定義一個服務,name是契約實現類的全名 -->

  7.    <servicename="Server.DataProvider">

  8.    <!-- 既然要對外提供服務,就要有服務地址,此處定義爲 http://localhost:8080/wcf,須要注意,地址老是帶着類型標頭的 -->

  9.      <host>

  10.        <baseAddresses>

  11.          <addbaseAddress="http://localhost:8080/wcf"/>

  12.        </baseAddresses>

  13.      </host>

  14.      <!-- 定義一下終節點,address通常爲空,若是不爲空,最終服務地址就是在baseAddress的基礎上加上這個address,binding指定爲basicHttpBinding,這是最基礎的基於http的綁定方式,contract標明這是爲哪一個契約服務 -->

  15.      <endpointaddress=""binding="basicHttpBinding"contract="Server.IData"/>

  16.    </service>

  17.  </services>

  18. </system.serviceModel>

  19. </configuration>


萬事具有,只剩最後一步了,將服務發佈出去:

  1. using System;

  2. using System.ServiceModel;

  3. namespace Server

  4. {

  5.    class Program

  6.    {

  7.        static void Main(string[] args)

  8.        {

  9.            //定義一個ServiceHost,注意參數中要使用契約實現類而不是接口

  10.            using(ServiceHost host = new ServiceHost(typeof(Server.DataProvider)))

  11.            {

  12.                host.Open();

  13.                Console.WriteLine("Service Running ...");

  14.                Console.ReadKey();

  15.                host.Close();

  16.            }

  17.        }

  18.    }

  19. }

有人可能會問服務發佈到哪去了?沒指定地址呀?這是一個初學者容易搞不明白的地方。

是 的,此時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" 看一下,應該有以下輸出:

  1. TCP    0.0.0.0:8080         0.0.0.0:0          LISTENING      4

  2. 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文件,這裏面要定義客戶端訪問的相關參數,這裏我去掉了一些用不上的參數,以保持配置文件簡單,防止各位看暈了頭。

  1. <?xmlversion="1.0"encoding="utf-8"?>

  2. <configuration>

  3.  <system.serviceModel>

  4.    <!-- 看到client,就代表是客戶端設置 -->

  5.    <client>

  6.      <!-- 定義訪問時的終節點,name也是隨意取的,注意address是Server端發佈時指定的baseAddress+endpoint的address,binding也要對應,contract就更不用說了,因爲以前把IData.cs拷過來的時候沒有修改命名空間,因此仍是Server.IData -->

  7.      <endpointname="DataService"address="http://localhost:8080/wcf"binding="basicHttpBinding"contract="Server.IData"/>

  8.    </client>

  9.  </system.serviceModel>

  10. </configuration>


而後寫代碼,來調用Server端發佈的SayHello方法:

  1. using System;

  2. using System.ServiceModel;

  3. using System.ServiceModel.Channels;

  4. namespace Client

  5. {

  6.    class Program

  7.    {

  8.        static void Main(string[] args)

  9.        {

  10.            //客戶端訪問有多種方式,此處只顯示一種

  11.            //利用ChannelFactory的CreateChannel方法建立一個IData的代理對象,其中參數「DataService」就是剛纔在App.config中定義的endpoint的名稱

  12.            var proxy = new ChannelFactory<Server.IData>("DataService").CreateChannel();

  13. //調用SayHello方法

  14.            Console.WriteLine(proxy.SayHello("WCF"));

  15. //用完後必定要關閉,由於服務端有最大鏈接數,不關閉會在必定時間內一直佔着有效鏈接

  16.            ((IChannel)proxy).Close();

  17.        }

  18.    }

  19. }

編譯運行,屏幕應能正常打印出「Hello WCF.」。第一個入門demo就搞定了,應該仍是比較簡單的。只是App.config的配置有些複雜,後面咱們會看到,其實也能夠不要配置,直接用代碼搞定,不過從鬆散耦合的角度講不建議這麼作。

 

第二篇:聊聊binding

 

上一篇構建的WCF程序,binding指定的是basicHttpBinding,這是最基礎的通信方式,基於http,不加密,抓一下包的話,應該是這樣的:

 

  1. 發送包: 
  2. POST /wcf HTTP/1.1 
  3. Content-Type: text/xml; charset=utf-8 
  4. SOAPAction: "WCF.Demo/IData/SayHello" 
  5. Host: 127.0.0.1:8080 
  6. Content-Length: 156 
  7. Expect: 100-continue 
  8. Connection: Keep-Alive 
  9.  
  10. <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><SayHello xmlns="WCF.Demo"><userName>WCF</userName></SayHello></s:Body></s:Envelope> 
  11.  
  12. -------------------------
  13. 應答包: 
  14. HTTP/1.1 100 Continue 
  15.  
  16. HTTP/1.1 200 OK 
  17. Content-Length: 191 
  18. Content-Type: text/xml; charset=utf-8 
  19. Server: Microsoft-HTTPAPI/2.0 
  20. Date: Mon, 05 Mar 2012 08:45:31 GMT 
  21.  
  22. <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><SayHelloResponse xmlns="WCF.Demo"><SayHelloResult>Hello WCF.</SayHelloResult></SayHelloResponse></s:Body></s:Envelope> 

 

就是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:

 

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <configuration>  
  3.   <system.serviceModel>  
  4.     <services>  
  5.       <service name="Server.DataService">  
  6.         <host>  
  7.           <baseAddresses>  
  8.             <add baseAddress="http://localhost:8080/wcf" /> 
  9.             <!-- 此處增長了個baseAddress,注意標頭指定的是net.tcp,另外端口不能被佔用 --> 
  10.             <add baseAddress="net.tcp://localhost:8081/wcf" /> 
  11.           </baseAddresses>  
  12.         </host>  
  13. <!-- 此處調整了綁定使用wsHttpBinding方式 --> 
  14.         <endpoint address="" binding="wsHttpBinding" contract="Server.IData" /> 
  15.         <!-- 此處增長了一個endpoint,使用netTcpBinding方式,服務契約一樣是IData --> 
  16.         <endpoint address="" binding="netTcpBinding" contract="Server.IData" /> 
  17.       </service>  
  18.     </services>  
  19.   </system.serviceModel>  
  20. </configuration>  

 

與以前相比,增長了一個baseAddress和一個endpoint,另外調 整了以前endpoint的綁定方式。如今針對同一個服務契約,有兩個endpoint了,可是它們不會衝突,由於二者的網絡協議不一樣,因此 wsHttpBinding的會使用http://localhost:8080/wcf的地址,而netTcpBinding的會使用net.tcp: //localhost:8081/wcf的地址。

 

若是同時定義了basicHttpBinding和wsHttpBinding呢?那麼它們的address必定不能相同,由於baseAddress已經相同了,adress還同樣,就沒法訪問了。

 

編譯運行一下,而後命令行「netstat -ano」看一下,應該能看到8080和8081都開始監聽了。

 

  1. TCP    0.0.0.0:8080        0.0.0.0:0          LISTENING       4 
  2. TCP    0.0.0.0:8081        0.0.0.0:0          LISTENING       692 
  3. TCP    [::]:8080           [::]:0             LISTENING       4 
  4. TCP    [::]:8081           [::]:0             LISTENING       692 

 

TCP 8081端口的PID顯示是咱們的服務程序在對外監聽。

 


二、客戶端

 

因爲服務端修改了綁定方式,客戶端必需要與之匹配,先修改App.config文件:

 

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <configuration>  
  3.   <system.serviceModel>  
  4.     <client>  
  5.       <!-- 此處調整了binding爲wsHttpBinding --> 
  6.       <endpoint name="httpDataService" address="http://localhost:8080/wcf" binding="wsHttpBinding" contract="Server.IData" /> 
  7.       <!-- 此處新增了一個endpoint,指定使用netTcpBinding方式 --> 
  8.       <endpoint name="tcpDataService" address="net.tcp://localhost:8081/wcf" binding="netTcpBinding" contract="Server.IData" /> 
  9.     </client>  
  10.   </system.serviceModel>  
  11. </configuration>  

 

須要指出的是,服務端開放了2種訪問方式,客戶端不必定也要寫2個endpoint,這裏是爲了測試兩種綁定。

 

調用代碼也要作個小修改:

 

  1. using System; 
  2. using System.ServiceModel; 
  3. using System.ServiceModel.Channels; 
  4.  
  5. namespace Client 
  6.     class Program 
  7.     { 
  8.         static void Main(string[] args) 
  9.         { 
  10.          //定義一個http方式的代理,配置使用httpDataService中的定義 
  11.             var httpProxy = new ChannelFactory<Server.IData>("httpDataService").CreateChannel(); 
  12.             Console.WriteLine(httpProxy.SayHello("WCF")); 
  13.             ((IChannel)httpProxy).Close(); 
  14.  
  15.          //定義一個tcp方式的代理,配置使用tcpDataService中的定義 
  16.             var tcpProxy = new ChannelFactory<Server.IData>("tcpDataService").CreateChannel(); 
  17.             Console.WriteLine(tcpProxy.SayHello("WCF")); 
  18.             ((IChannel)tcpProxy).Close(); 
  19.         } 
  20.     } 

 


編譯運行一下,應該可以輸出兩行 Hello WCF.。

 

抓包看看:

 

wsHttpBinding方式的客戶端與服務端總共交換了60多個數據包,這是由於雙方要先握手交換密鑰,另外因爲數據加了密,長度也變大了。這裏只截第一次交互的數據看看:

 

  1. 發送包: 
  2. POST /wcf HTTP/1.1 
  3. Content-Type: application/soap+xml; charset=utf-8 
  4. Host: 127.0.0.1:8080 
  5. Content-Length: 1106 
  6. Expect: 100-continue 
  7. Connection: Keep-Alive 
  8.  
  9. <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"><s:Header><a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action><a:MessageID>urn:uuid:144b8aeb-6ac1-46f5-8361-957425b827c8</a:MessageID><a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo><a:To s:mustUnderstand="1">http://192.168.90.81:8080/wcf</a:To></s:Header><s:Body><t:RequestSecurityToken Context="uuid-156d27d6-3db7-43ac-b488-12f9ae123861-1" xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust"><t:TokenType>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</t:TokenType><t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType><t:KeySize>256</t:KeySize><t:BinaryExchange ValueType="http://schemas.xmlsoap.org/ws/2005/02/trust/spnego" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">TlRMTVNTUAABAAAAt7IY4gkACQAxAAAACQAJACgAAAAGAbAdAAAAD1RJQU5ZVS1QQ1dPUktHUk9VUA==</t:BinaryExchange></t:RequestSecurityToken></s:Body></s:Envelope> 
  10.  
  11. --------------------- 
  12. 應答包: 
  13. HTTP/1.1 100 Continue 
  14.  
  15. HTTP/1.1 200 OK 
  16. Content-Length: 1044 
  17. Content-Type: application/soap+xml; charset=utf-8 
  18. Server: Microsoft-HTTPAPI/2.0 
  19. Date: Tue, 06 Mar 2012 01:46:24 GMT 
  20.  
  21. <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"><s:Header><a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue</a:Action><a:RelatesTo>urn:uuid:144b8aeb-6ac1-46f5-8361-957425b827c8</a:RelatesTo></s:Header><s:Body><t:RequestSecurityTokenResponse Context="uuid-156d27d6-3db7-43ac-b488-12f9ae123861-1" xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><t:BinaryExchange ValueType="http://schemas.xmlsoap.org/ws/2005/02/trust/spnego" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">TlRMTVNTUAACAAAAEgASADgAAAA1wprik+hmjwEji3X4ciEAAAAAAGgAaABKAAAABgGwHQAAAA9UAEkAQQBOAFkAVQAtAFAAQwACABIAVABJAEEATgBZAFUALQBQAEMAAQASAFQASQBBAE4AWQBVAC0AUABDAAQAEgBUAGkAYQBuAFkAdQAtAFAAQwADABIAVABpAGEAbgBZAHUALQBQAEMABwAIAPm5CvA6+8wBAAAAAA==</t:BinaryExchange></t:RequestSecurityTokenResponse></s:Body></s:Envelope> 

 

應該是雙方交換了一個256位的密鑰,反正全部的數據交互都再也不是明文的了。

 

再來看netTcpBinding的此次,仍是隻截一小段吧:

 

  1. 發送包: 
  2. 00000000  00 01 00 01 02 02 20 6E  65 74 2E 74 63 70 3A 2F   ...... n et.tcp:/  
  3. 00000010  2F 31 39 32 2E 31 36 38  2E 39 30 2E 38 31 3A 38   /192.168 .90.81:8  
  4. 00000020  30 38 31 2F 77 63 66 03  08 09 15 61 70 70 6C 69   081/wcf. ...appli  
  5. 00000030  63 61 74 69 6F 6E 2F 6E  65 67 6F 74 69 61 74 65   cation/n egotiate  
  6. 00000040  16 01 00 00 3A 4E 54 4C  4D 53 53 50 00 01 00 00   ....:NTL MSSP....  
  7. 00000050  00 B7 B2 18 E2 09 00 09  00 31 00 00 00 09 00 09   ........ .1..... 
  8.  
  9. ---------- 
  10. 應答包: 
  11. 00000000  0A 16 01 00 00 B2 4E 54  4C 4D 53 53 50 00 02 00   ......NT LMSSP...  
  12. 00000010  00 00 12 00 12 00 38 00  00 00 35 C2 9A E2 3A D5   ......8. ..5...:.  
  13. 00000020  19 64 33 D4 B9 7C F8 72  21 00 00 00 00 00 68 00   .d3..|.r !.....h.  
  14. 00000030  68 00 4A 00 00 00 06 01  B0 1D 00 00 00 0F 54 00   h.J..... ......T.  
  15. 00000040  49 00 41 00 4E 00 59 00  55 00 2D 00 50 00 43 00   I.A.N.Y. U.-.P.C.  
  16. 00000050  02 00 12 00 54 00 49 00  41 00 4E 00 59 00 55 00   ....T.I. A.N.Y.U.  

 

從效率上講,tcp方式要比http方式高得多,同時http方式,basicHttpBinding又要比wsHttpBinding高得多,在實際使用中,你們要看需求來決定使用哪一種方式。

 

OK,下一篇咱們看看能不能不用寫複雜的配置就發佈WCF服務,也以此來加深一下對Host、Contract、Binding、BaseAddress的理解。

 

通常狀況下調用遠程WebService經過代理類直接訪問就能夠,但是若是WebService是在https站點下,調用時就要分狀況考慮了,整理了一下:

 

 

 

一、客戶端證書已正確安裝

 

指已經在客戶端安裝了客戶端證書到證書存儲區,且證書符合如下幾個條件:

 

  ◆ 證書中定義的使用者與訪問WebService時使用的域名一致;
  ◆ 證書未過時;
  ◆ 證書鏈在本機完整可信;

 

關於證書鏈完整可信,是指本證書,以及向上追溯的各級頒發者,直至根證書頒發者,都被系統認可。

 

此時直接調用便可,與訪問http時沒有區別,底層會自動處理SSL握手。若是有任何一項不符合要求,調用時就會產生「基礎鏈接已經關閉:未能爲 SSL/TLS 安全通道創建信任關係」的異常,此時要使用2中的方法。

 

二、客戶端證書未正確安裝

 

  1. public void CallRemoteWebService()  
  2.     //指定證書驗證回調方法,Lamda表達式,直接返回true,表示忽略全部錯誤  
  3.     ServicePointManager.ServerCertificateValidationCallback = (source, cert, chain, errors) => true
  4.   
  5.     //建立WebService的代理實例  
  6.     var proxy = new Proxy();  
  7.     //遠程調用  
  8.     proxy.DoSomething();  
  9. }  

 

關鍵在於要讓證書驗證時忽略全部錯誤。不用擔憂,忽略錯誤只是指不用判斷證書有效性,並不會影響通訊信道的加密過程。

 

第三篇:試着去掉配置文件

 

經過配置文件來設置Host、Endpoint、Binding等是WCF中推 薦的方法,這樣可使發佈儘可能靈活。其實配置文件中的值,最終仍是要體現到代碼中的,只不過這部分工做由底層幫你作了。咱們今天來嘗試去掉配置文件,用純 代碼實現發佈過程,同時加深一下對層次關係的理解。

 

一、服務端

 

在上回的基礎上刪掉App.config吧,而後把Main方法修改一下:

 

  1. using System;  
  2. using System.ServiceModel;  
  3.   
  4. namespace Server  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             //定義兩個基地址,一個用於http,一個用於tcp 
  11.             Uri httpAddress = new Uri("http://localhost:8080/wcf"); 
  12.             Uri tcpAddress = new Uri("net.tcp://localhost:8081/wcf"); 
  13.             //服務類型,注意一樣是實現類的而不是契約接口的 
  14.             Type serviceType = typeof(Server.DataProvider); 
  15.  
  16.             //定義一個ServiceHost,與以前相比參數變了 
  17.             using(ServiceHost host = new ServiceHost(serviceType, new Uri[] { httpAddress, tcpAddress }))  
  18.             {  
  19.                 //定義一個basicHttpBinding,地址爲空 
  20.                 Binding basicHttpBinding = new BasicHttpBinding(); 
  21.                 string address = ""
  22.                 //用上面定義的binding和address,建立endpoint 
  23.                 host.AddServiceEndpoint(typeof(Server.IData), basicHttpBinding, address); 
  24.  
  25.                 //再來一個netTcpBinding 
  26.                 Binding netTcpBinding = new NetTcpBinding(); 
  27.                 address = ""
  28.                 host.AddServiceEndpoint(typeof(Server.IData), netTcpBinding, address); 
  29.  
  30.                 //開始服務 
  31.                 host.Open();  
  32.                 Console.WriteLine("Service Running ...");  
  33.                 Console.ReadKey();  
  34.                 host.Close();  
  35.             }  
  36.         }  
  37.     }  

 

若是咱們把代碼和以前的App.config對比着的地一下,就會發現元素是對應的。咱們來整理一下目前爲止出現的層級關係:

 

 ServiceHost
   ├ ServiceType       實現類的類型
   ├ Uri[]                   基地址,對應config中的<baseAddresses>
   └ ServiceEndpoint[]       服務終結點,對應config中的多個<endpoint>
        ├ ServiceContract    服務契約,對應config中<endpoint>的contract屬性
        ├ Binding            綁定,對應config中<endpoint>的binding屬性
        └ EndpointAddress    終結點地址,對應config中<endpoint>的address屬性

 


二、客戶端

 

一樣能夠刪掉App.config了,代碼改一下:

 

  1. using System;  
  2. using System.ServiceModel;  
  3. using System.ServiceModel.Channels;  
  4.   
  5. namespace Client  
  6. {  
  7.     class Program  
  8.     {  
  9.         static void Main(string[] args)  
  10.         {  
  11.             //定義綁定與服務地址 
  12.             Binding httpBinding = new BasicHttpBinding(); 
  13.             EndpointAddress httpAddr = new EndpointAddress("http://localhost:8080/wcf"); 
  14.             //利用ChannelFactory建立一個IData的代理對象,指定binding與address,而不用配置文件中的  
  15.             var proxy = new ChannelFactory<Server.IData>(httpBinding, httpAddr).CreateChannel();  
  16.             //調用SayHello方法並關閉鏈接 
  17.             Console.WriteLine(proxy.SayHello("WCF"));  
  18.             ((IChannel)proxy).Close();  
  19.  
  20.             //換TCP綁定試試 
  21.             Binding tcpBinding = new NetTcpBinding(); 
  22.             EndpointAddress tcpAddr = new EndpointAddress("net.tcp://localhost:8081/wcf"); 
  23.             var proxy2 = new ChannelFactory<Server.IData>(httpBinding, httpAddr).CreateChannel();  
  24.             Console.WriteLine(proxy2.SayHello("WCF"));  
  25.             ((IChannel)proxy2).Close();  
  26.     }  
  27. }  

 

對照着上面,也來比對一下代碼中現出的對象與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想傳遞自定義數據,必需要將其定義爲數據契約。看一個例子:

 

  1. using System; 
  2. using System.Runtime.Serialization;  //注意加上這個引用,並引用相應的dll 
  3.  
  4. namespace Server 
  5.     //用DataContract來標記本類是數據契約類 
  6.     [DataContract] 
  7.     public class UserEntity 
  8.     { 
  9.         //用DataMember來標識數據成員,沒有此標識的屬性不會做爲數據契約的一部分 
  10.         [DataMember] 
  11.         public string Name { getset; } 
  12.         [DataMember] 
  13.         public int Age { getset; } 
  14.     } 

 

這個契約須要在客戶端和服務端都存在,而後它就能夠做爲參數或返回值,在雙方互相傳遞了,具體例子就省略了。

 

這裏面有一點須要注意,數據契約與服務契約有一點小小的區別,數據契約要求在客戶端和服務端必須保持徹底一致的類名與命名空間,不然就沒法傳遞數據,這與服務契約是不一樣的,服務契約放到客戶端時容許換個命名空間。

 

組內有同事遇到過相似的問題,直接把數據契約類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沒什麼實際用處,裏面也只有一行代碼:

 

  1. <%@ 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的代理類。

 

 

在IIS中發佈WCF服務,通常不多用http以外的binding,但不表明不能用,IIS是支持的,咱們在IIS管理器中查看一下站點綁定,默認狀況下能看到這樣的設置:

 

像netTcpBinding,就會利用IIS的net.tcp類型綁定,端口是808。

 

第六篇:單向與雙向通信

 

項目開發中咱們時常會遇到須要異步調用的問題,有時忽略服務端的返回值,有時但願服務端在須要的時候回調,今天就來看看在WCF中如何實現。

 

先看不須要服務端返回值的單向調用,老規矩,直接上代碼,再解釋。

 

一、服務端

 

契約接口中增長一個Sleep方法:

  1. using System;  
  2. using System.ServiceModel;  
  3. using System.Text;  
  4.   
  5. namespace Server  
  6. {  
  7.     [ServiceContract(Namespace="WCF.Demo")]  
  8.     public interface IData  
  9.     {  
  10.         [OperationContract]  
  11.         string SayHello(string userName);  
  12.  
  13.         /// <summary> 
  14.         /// IsOneWay = true 代表這是一個單向調用,注意返回值是void,由於既然是單向調用,客戶端確定不會等待接收返回值的 
  15.         /// </summary> 
  16.         [OperationContract(IsOneWay = true)] 
  17.         void Sleep(); 
  18.     }  

 


對應的實現類中,咱們來實現這個方法:

  1. using System;  
  2. using System.Text;  
  3.   
  4. namespace Server  
  5. {  
  6.     public class DataProvider : IData  
  7.     {  
  8.         public string SayHello(string userName)  
  9.         {  
  10.             return string.Format("Hello {0}.", userName);  
  11.         }  
  12.  
  13.         /// <summary> 
  14.         /// 實現Sleep方法,暫時不作任何事情,只是睡眠5秒 
  15.         /// </summary> 
  16.         public void Sleep() 
  17.         { 
  18.             Thread.Sleep(5000); 
  19.         } 
  20.     }  
  21. }  

 

App.config就再也不列出來了,裏面用的是一個netTcpBinding的endpoint。


二、客戶端

 

首先別忘了客戶端的契約要與服務端保持一致,App.config也不列出來了,裏面有對應的endpoint。主要是調用的代碼:

  1. using System;  
  2. using System.ServiceModel;  
  3. using System.ServiceModel.Channels;  
  4.   
  5. namespace Client  
  6. {  
  7.     class Program  
  8.     {  
  9.         static void Main(string[] args)  
  10.         {  
  11.             var proxy = new ChannelFactory<Server.IData>("DataService").CreateChannel();  
  12.  
  13.             //先調用SayHello方法  
  14.             Console.WriteLine(proxy.SayHello("WCF"));  
  15.             //調用一下Sleep方法,按咱們的設想,它應該是異步的,因此不會阻塞後面的調用 
  16.             proxy.Sleep(); 
  17.             //再調用一次SayHello方法  
  18.             Console.WriteLine(proxy.SayHello("WCF"));  
  19.             //關閉鏈接  
  20.             ((IChannel)proxy).Close();  
  21.     }  

 

按咱們的設想,兩次SayHello調用之間應該沒有延遲,由於Sleep是異步的嘛,編譯運行一下,結果……  中間卡住了5秒,這是爲何呢?

 

這其中涉及到一個併發模型的問題,默認狀況下,WCF以單線程模型對外提供服務,也就是說,只能一個一個處理請求,即便是一個OneWay的單向調用,也只能等它處理完後纔會接着處理後面的SayHello請求,因此會卡5秒。

 

併發模式有如下三種,MSDN上的介紹有點複雜,我給簡化一下:

Single:單線程調用,請求只能一個一個處理;

Reentrant:可重入的單線程調用,本質還是單線程,處理回調時,回調請求會進入隊列尾部排隊;

Multiple:多線程調用,請求是併發的響應的;

 


調置服務併發模型是在契約的實現類上,咱們爲DataService類加一個Attribute:

  1. /// <summary> 
  2. /// 用ServiceBehavior爲契約實現類標定行爲屬性,此處指定併發模型爲ConcurrencyMode.Multiple,即併發訪問 
  3. /// </summary> 
  4. [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)] 
  5. public class DataProvider : IData 
  6.     //略 

 


這回再編譯運行一下,連續打出了2行 Hello WCF,中間再也不阻塞了。

 

 

如今咱們再來看看雙向通信的問題。雙向通信能夠基於HTTP、TCP、 Named Pipe、MSMQ,但要注意,basicHttpBinding和wsHttpBinding不行,要換用wsDualHttpBinding,它會創 建兩個鏈接來進行雙向通信。至於TCP,它自然就是雙向通信的。

 

一、服務端

 

服務契約要進行修改,增長關於回調的契約:

  1. using System; 
  2. using System.ServiceModel; 
  3. using System.ServiceModel.Description; 
  4.  
  5. namespace Server 
  6.     /// <summary> 
  7.     /// 增長了CallbackContract的標識,用於指明針對此服務契約的回調契約是IDataCallback 
  8.     /// </summary> 
  9.     [ServiceContract(Namespace = "WCF.Demo", CallbackContract = typeof(IDataCallback))] 
  10.     public interface IData 
  11.     { 
  12.         [OperationContract] 
  13.         string SayHello(string userName); 
  14.  
  15.         [OperationContract(IsOneWay = true)] 
  16.         void Sleep(); 
  17.     } 
  18.  
  19.     /// <summary> 
  20.     /// 定義服務回調契約,注意它沒有契約標識,只是個通常接口 
  21.     /// </summary> 
  22.     public interface IDataCallback 
  23.     { 
  24.         /// <summary> 
  25.         /// 定義一個回調方法,因爲回調不可能要求對方再響應,因此也標識成OneWay的調用,一樣不須要有返回值 
  26.         /// </summary> 
  27.        [OperationContract(IsOneWay = true)] 
  28.        void SleepCallback(string text); 
  29.     } 

 


對應的契約實現類要修改一下:

  1. using System; 
  2. using System.ServiceModel; 
  3. using System.ServiceModel.Description; 
  4. using System.Threading; 
  5. using System.Net; 
  6.  
  7. namespace Server 
  8.     [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)] 
  9.     public class DataProvider : IData 
  10.     { 
  11.         public string SayHello(string userName) 
  12.         { 
  13.             string.Format("Hello {0}.", userName); 
  14.         } 
  15.  
  16.         public void Sleep() 
  17.         { 
  18. //先睡5秒
  19.             Thread.Sleep(5000); 
  20.  
  21. //用OperationContext.Current來獲取指定類型的回調對象 
  22.             var callback = OperationContext.Current.GetCallbackChannel<IDataCallback>(); 
  23. //回調SleepCallback方法,並傳遞參數 
  24.             callback.SleepCallback("睡醒了"); 
  25.         } 
  26.     } 

 


二、客戶端

 

仍然提醒一下別忘了把新的服務契約更新到客戶端。客戶端的調用要調整一下:

  1. using System;  
  2. using System.ServiceModel;  
  3. using System.ServiceModel.Channels;  
  4.   
  5. namespace Client  
  6. {  
  7.     class Program  
  8.     {  
  9.         static void Main(string[] args)  
  10.         {  
  11.             //定義一個實現回調接口的類實例 
  12.             var context = new DataCallbackImp(); 
  13.             //建立代理的時候變了,要用DuplexChannelFactory,由於IData契約已經標識了有回調,因此必需要用支持雙向通信的ChannelFactory,傳入剛纔建立的回調實例 
  14.             var proxy = new DuplexChannelFactory<Server.IData>(context, "DataService").CreateChannel();  
  15.  
  16.             //調用Sleep 
  17.             proxy.Sleep(); 
  18.             //調用SayHello方法  
  19.             Console.WriteLine(proxy.SayHello("WCF"));  
  20.  
  21.             //等待按任意鍵,先不要關鏈接 
  22.             Console.ReadKey(); 
  23.             ((IChannel)proxy).Close();  
  24.     }  
  25.  
  26.     /// <summary> 
  27.     /// 實現回調接口中的類,圖省事寫到這裏了 
  28.     /// </summary> 
  29.     class DataCallbackImp : Server.IDataCallback 
  30.     { 
  31.         /// <summary> 
  32.         /// 實現SleepCallback方法 
  33.         /// </summary> 
  34.         public void SleepCallback(string text) 
  35.         { 
  36.             Console.WriteLine("收到回調了:" + text); 
  37.         } 
  38.     } 

 


編譯運行,屏幕先顯示一行「Hello WCF.」,過5秒後顯示「收到回調了:睡醒了」。

 

第七篇:併發模型與實例模型

 

在以往使用WebService時,針對每個請求,服務類老是併發響應的,而且對每一個請求都生成新的實例。在WCF中,狀況發生變化了,它容許服務發佈者自定義並組合併發模型與實例模型。

 

併發模型有三種:

 

ConcurrencyMode

Single:   單線程模型,能夠理解爲,針對一個客戶端,只有一個線程負責響應;
Reentrant:可重入的單線程模型,與Single的區別在於,對於OneWay/回調,它不會阻塞,而是把回調的線程放到隊列尾部等着最後處理;
Multiple: 多線程模型,能夠理解爲,針對一個客戶端,也容許併發訪問;

 

 

 

實例模型也有三種:

 

InstanceContextMode

PerCall:   針對每次調用都生成新的服務實例;
PerSession:針對一個會話生成一個服務實例;
Single:    針對全部會話和全部調用共用同一個服務實例;

 

組合起來是什麼效果呢?咱們來用示例代碼驗證一下。

 

一、服務端

服務契約,具體解釋見實現類吧,只要注意一下Sleep方法定義成了IsOneWay=true:

  1. using System; 
  2. using System.ServiceModel; 
  3. using System.ServiceModel.Description; 
  4.  
  5. namespace Server 
  6.     [ServiceContract(Namespace = "WCF.Demo")] 
  7.     public interface IData 
  8.     { 
  9.         [OperationContract] 
  10.         int GetCounter(); 
  11.  
  12.         [OperationContract(IsOneWay = true)] 
  13.         void Sleep(); 
  14.     } 

 


契約實現類:

  1. using System; 
  2. using System.ServiceModel; 
  3. using System.ServiceModel.Description; 
  4. using System.Threading; 
  5. using System.Net; 
  6.  
  7. namespace Server 
  8.     //Single併發模式 + PerCall實例模式,針對後面的測試要修改這兩個值的組合 
  9.     [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single, InstanceContextMode = InstanceContextMode.PerCall)] 
  10.     public class DataProvider : IData 
  11.     { 
  12.         //定義一個計數器,對每一個新生成的服務實例,它都是0,咱們經過它來判斷是否新實例 
  13.         public int Counter { getset; } 
  14.  
  15.         //獲取計數器,並自增計數器 
  16.         public int GetCounter() 
  17.         { 
  18.             return ++Counter; 
  19.         } 
  20.  
  21.         //睡眠2秒 
  22.         public void Sleep() 
  23.         { 
  24.             Thread.Sleep(2000); 
  25.         } 
  26.     } 

 

App.config不列了,用的是netTcpBinding。


二、客戶端:

 

別的不列了,只列一下調用代碼:

  1. using System; 
  2. using System.ServiceModel; 
  3. using System.ServiceModel.Channels; 
  4. using System.Threading; 
  5.  
  6. namespace Client 
  7.     class Program 
  8.     { 
  9.         static void Main(string[] args) 
  10.         { 
  11. //啓動3個線程併發訪問 
  12.             for(int i = 0; i < 3; ++i) 
  13.             { 
  14.                 var thread = new Thread(() => 
  15.                 { 
  16.                     string name = Thread.CurrentThread.Name; 
  17.  
  18.                     var proxy = new ChannelFactory<Server.IData>("DataProvider").CreateChannel(); 
  19.  
  20. //先調用GetCounter方法,再調用Sleep方法,而後再調一次GetCounter方法
  21.                     Console.WriteLine(string.Format("{0}: {1}  {2}", name, proxy.GetCounter(), DateTime.Now.ToString("HH:mm:ss.fff")));
  22.   proxy.Sleep(); 
  23.                     Console.WriteLine(string.Format("{0}: {1}  {2}", name, proxy.GetCounter(), DateTime.Now.ToString("HH:mm:ss.fff"))); 
  24.                     ((IChannel)proxy).Close(); 
  25.                 }); 
  26.  
  27. //定義一下線程名,方便識別 
  28.                 thread.Name = "線程" + i; 
  29.                 thread.Start(); 
  30.             } 
  31.         } 
  32.     } 

 

OK,開始驗證:

 

一、ConcurrencyMode.Single + InstanceContextMode.PerCall

執行結果以下:

  1. 線程1: 1  15:56:05.262 
  2. 線程2: 1  15:56:05.262 
  3. 線程0: 1  15:56:05.263 
  4. 線程1: 1  15:56:07.263 
  5. 線程2: 1  15:56:07.263 
  6. 線程0: 1  15:56:07.264 

 

首先,打印出的 Counter全是1,說明針對每次請求,服務端的契約實現類(DataProvider)都是新實例化的。其次,同一個線程的兩次GetCounter 請求相隔了2秒,說明針對一個客戶端的調用阻塞了。再次,三個線程幾乎同時完成調用,說明它們之間並未互相阻塞。

二、ConcurrencyMode.Single + InstanceContextMode.PerSession

 

執行結果以下:

  1. 線程0: 1  16:02:46.173 
  2. 線程1: 1  16:02:46.173 
  3. 線程2: 1  16:02:46.173 
  4. 線程1: 2  16:02:48.174 
  5. 線程2: 2  16:02:48.174 
  6. 線程0: 2  16:02:48.174 

與上面相比,區別在於同一個線程的Counter在第二次調用時變成2了,說明針對同一個客戶端的兩次調用使用的是同一個服務實例。

 

三、ConcurrencyMode.Single + InstanceContextMode.Single

執行結果以下:

  1. 線程1: 2  16:05:46.270 
  2. 線程0: 1  16:05:46.270 
  3. 線程2: 3  16:05:46.270 
  4. 線程1: 4  16:05:52.273 
  5. 線程0: 5  16:05:52.273 
  6. 線程2: 6  16:05:52.274 

 

與上面相比,區別在於Counter一直在增加,這說明在服務端自始至終只有一個服務實例,它來響應全部的會話全部的請求。

四、ConcurrencyMode.Reentrant + InstanceContextMode.PerCall

 

執行結果以下:

  1. 線程1: 1  16:07:42.505 
  2. 線程2: 1  16:07:42.506 
  3. 線程2: 1  16:07:42.507 
  4. 線程1: 1  16:07:42.507 
  5. 線程0: 1  16:07:42.505 
  6. 線程0: 1  16:07:42.507 

和1的區別在於兩次GetCounter調用之間沒有2秒的延遲,這是因爲Reentrant模式下,回調被放入隊列尾部再處理,不會阻塞後面的調用。而且針對同一客戶端的每一個請求都是不一樣的服務實例在處理,不會阻塞。

 

五、ConcurrencyMode.Reentrant + InstanceContextMode.PerSession

執行結果以下:

  1. 線程2: 1  16:27:44.699 
  2. 線程0: 1  16:27:44.700 
  3. 線程1: 1  16:27:44.699 
  4. 線程0: 2  16:27:46.700 
  5. 線程1: 2  16:27:46.700 
  6. 線程2: 2  16:27:46.700 

 

與上面相比,區別在於又有了2秒的阻塞,這是因爲針對一個客戶端的屢次請求,是 同一個服務實例在處理,雖然容許重入,但只有一個對象,執行順序是:第一次GetCounter->Sleep(不阻塞)->Sleep回調 ->第二次GetCounter,因此表現上仍是阻塞住了。

六、ConcurrencyMode.Reentrant + InstanceContextMode.Single

 

執行結果以下:

  1. 線程0: 1  16:34:01.417 
  2. 線程1: 3  16:34:01.418 
  3. 線程2: 2  16:34:01.417 
  4. 線程0: 4  16:34:05.420 
  5. 線程1: 5  16:34:07.420 
  6. 線程2: 6  16:34:07.420 

自始至終只有一個服務實例,執行順序應該是:線程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

 

執行結果以下:

  1. 線程2: 1  17:07:05.639 
  2. 線程1: 1  17:07:05.639 
  3. 線程0: 1  17:07:05.639 
  4. 線程2: 1  17:07:05.640 
  5. 線程1: 1  17:07:05.641 
  6. 線程0: 1  17:07:05.641 

 

屢次調用徹底是併發的,每次調用的實例也是新建立的。

八、ConcurrencyMode.Multiple + InstanceContextMode.PerSession

 

執行結果以下:

  1. 線程1: 1  17:09:10.285 
  2. 線程0: 1  17:09:10.285 
  3. 線程1: 2  17:09:10.286 
  4. 線程0: 2  17:09:10.286 
  5. 線程2: 1  17:09:10.285 
  6. 線程2: 2  17:09:10.287 

屢次調用徹底併發,但針對同一個會話,實例是相同的。

 

九、ConcurrencyMode.Multiple + InstanceContextMode.Single

執行結果以下:

  1. 線程1: 1  17:16:46.543 
  2. 線程0: 3  17:16:46.543 
  3. 線程2: 2  17:16:46.543 
  4. 線程1: 4  17:16:46.544 
  5. 線程0: 5  17:16:46.544 
  6. 線程2: 6  17:16:46.544 

 

徹底併發,Counter也一直增加,代表自始至終是同一個服務實例。

相關文章
相關標籤/搜索