WCF把書讀薄(2)——消息交換、服務實例、會話與併發

  上一篇:WCF把書讀薄(1)——終結點與服務寄宿html

 

  8、消息交換模式緩存

  WCF服務的實現是基於消息交換的,消息交換模式一共有三種:請求回覆模式、單向模式與雙工模式。服務器

  請求回覆模式很好理解,好比int Add(int num1, int num2)這種方法定義就是典型的請求回覆模式,請求者發送兩個數字,服務回覆一個結果數字。若是採用ref或者out參數,那麼在xsd當中,ref參數會做爲輸入和輸出參數,out參數只做爲輸出參數。在WCF當中void返回值的操做契約其實也是請求響應模式的,由於將返回值改成void,影響的只是回覆消息的xsd結構,void返回的是一個空xml元素(P141)。併發

  對於一些調用服務記錄日誌等不要求有響應(即使拋異常也不須要客戶端知道)的行爲,應該採用單向模式,單向模式只須要在操做契約上添加單向的屬性:異步

[OperationContract(IsOneWay=true]
void WriteLog(string msg);

  單向模式的操做在對應的wsdl當中沒有輸出節點,這樣的操做必須使用void做爲返回值,其參數也不可以使用ref和out參數(P144)。函數

  最後一類是雙工模式,雙工模式是在服務端定義接口,由客戶端實現這個方法,服務端「回調」客戶端的這個方法。這裏直接扒書加法的例子,由於這個例子又簡單又能說明問題,這個例子當中客戶端調用服務端的加法,服務端回調客戶端的顯示函數。ui

  首先定義服務契約:spa

[ServiceContract(Namespace = "http://www.artech.com/", CallbackContract = typeof(ICalculatorCallback))]
public interface ICalculator
{
    [OperationContract(IsOneWay = true)]
    void Add(double x, double y);
}

這裏定義了CallbackContract屬性,須要傳入一個接口的名字,這個接口名字就是回調操做契約,既然在這裏指明瞭它是個契約,就無需服務契約標籤了,這裏之因此採用單向,是爲了防止死鎖:線程

public interface ICalculatorCallback
{
    [OperationContract(IsOneWay = true)]
    void DisplayResult(double result, double x, double y);
}

契約實現以下:代理

public class CalculatorService : ICalculator
{
    public void Add(double x, double y)
    {
        double result = x + y;
        ICalculatorCallback callback = OperationContext.Current.GetCallbackChannel<ICalculatorCallback>();
        callback.DisplayResult(result, x, y);
    }
}

注意實現的第二行,先從當前操做上下文當中拿到了回調信道,以後調用它的回調方法。

客戶端實現以下:

public class CalculatorService : ICalculator
{
    public void Add(double x, double y)
    {
        double result = x + y;
        ICalculatorCallback callback = OperationContext.Current.GetCallbackChannel<ICalculatorCallback>();
        callback.DisplayResult(result, x, y);
    }
}

首先是一個回調函數的實現類,它實現了回調契約,不過老A的例子有些不雅,這裏直接引了契約的dll。

而後是客戶端的主體:

class Program
{
    static void Main(string[] args)
    {
        InstanceContext callback = new InstanceContext(new CalculatorCallbackService());
        using (DuplexChannelFactory<ICalculator> channelFactory = new DuplexChannelFactory<ICalculator>(callback, "calculatorservice"))
        {
            ICalculator calculator = channelFactory.CreateChannel();
            calculator.Add(1, 2);
        }
        Console.Read();
    }
}

這裏首先建立了實例上下文,用它和終結點的配置一塊兒建立了雙工信道工廠,以後經過這個工廠建立信道來實現雙工調用(這裏不雅同上)。

  服務端的配置以下:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="exposeExceptionDetail">
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="Artech.WcfServices.Service.CalculatorService"
               behaviorConfiguration="exposeExceptionDetail">
        <endpoint address="http://127.0.0.1:3721/calculatorservice"
                  binding="wsDualHttpBinding"
                  contract="Artech.WcfServices.Service.Interface.ICalculator"/>
      </service>
    </services>
  </system.serviceModel>
</configuration>

這裏採用了支持雙工通訊的wsDualHttpBinding綁定,客戶端配置以下:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint name ="calculatorservice"
                address="http://127.0.0.1:3721/calculatorservice"
                binding="wsDualHttpBinding"
                contract="Artech.WcfServices.Service.Interface.ICalculator"/>
    </client>
  </system.serviceModel>
</configuration>

 

  9、實例與會話

  上面了例子裏有一個InstanceContext對象, 這個對象就是實例上下文,它是對服務實例的封裝,對於一個調用服務的請求,WCF會首先反射服務類型來建立服務實例,並用實例上下文對其進行封裝(固然這個實例是帶「緩存」的),咱們能夠配置必定的規則來釋放上下文(P396)。

  實例上下文分爲三種模式:單調模式、會話模式和單例模式。上下文的模式是服務的行爲,與客戶端無關,以[ServiceBehavior]的InstanceContextMode屬性來設置。下面分別來看一看這三種模式。

  單調模式,表示每一次調用服務都會建立一個全新的服務實例和上下文,上下文的生命週期與服務調用自己綁定在一塊兒(P402),這種方式能最大限度地發揮資源利用率,避免了資源的閒置和競爭,所以單調模式適合處理大量併發的客戶端(P406)。

  實現單調模式須要在服務的實現類上增長反射標記:

[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]
public class CalculatorService : ICalculator

  從這裏也能看出,服務的實現類並不表明業務邏輯,而是位於業務邏輯之上的一個「隔離層」,它顯然屬於服務層。

  單例模式則走了另外一個極端,這種模式讓整個服務器上自始至終只存在一個上下文,它的反射標籤是:

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]

  既然只有一個上下文,那麼說明同時只能處理一個請求,剩下的請求去排隊或者超時。這種模式只能應付不多的客戶端,並且僅限於作全局計數這樣的操做。若是須要讓這個服務異步執行,須要這樣寫反射標籤:

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,ConcurrencyMode=ConcurrencyMode.Multiple)]

  會話模式則將爲每個服務代理生成一個上下文,會話使服務具備識別客戶端的能力,因此必定要選用支持會話的綁定(P420),這種模式適合於客戶端數量不多的應用。

  這種模式的服務契約上面有SessionMode標籤,Required對服務的整個調用必須是一個會話,默認值爲Allowed,會在適當時機採用會話模式。服務契約含有IsInitiating和IsTerminating兩個屬性,在客戶端調用服務時,必須先調用IsInitiating爲true和IsTerminating爲false的,做爲起始,最終要調用IsInitiating爲false而IsTerminating爲true的,做爲終結,在二者之間能夠調用全爲false的操做。若是不這樣調用會報錯。

[ServiceContract(SessionMode=SessionMode.Required)]
public interface ICalculator
{
    [OperationContract(IsInitiating=true, IsTerminating=false)]
    void Reset();
    [OperationContract(IsInitiating = false, IsTerminating = false)]
    void Add(int num);
    [OperationContract(IsInitiating = false, IsTerminating = true)]
    int GetResult();
}

  服務實現以下,首先服務行爲加上了InstanceContextMode=InstanceContextMode.PerSession,並在服務的內部保存了一個叫作result的非靜態變量:

[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class CalculatorService : ICalculator
{
    private int result;
    public void Reset()
    {
        result = 0;
    }

    public void Add(int num)
    {
        result += num;
    }

    public int GetResult()
    {
        return result;
    }
}

  上面一共提到了InstanceContextMode和SessionMode兩個枚舉,當採用PerCall單調服務時,不論SessionMode如何,中間結果都不會被保存;採起Single單例服務時,不論SessionMode如何中間結果都會被保存,由於上下文是單例的;採起PerSession會話服務時,只有會話模式爲Required和Allowed時,中間結果纔會被保存。(P427)一張圖說明問題:

  

  

  10、併發

  服務行爲的InstanceContextMode表示的是對於一個請求,在服務端搞出幾個實例上下文來,那麼,ConcurrencyMode則表示同一個服務實例如何同時處理多個並行到來的請求,這些請求可能來自同一個服務代理的並行調用,也可能來自多個服務代理的同時調用。

  不過在使用ConcurrencyMode以前,須要先給服務/回調服務加上以下標記:

[ServiceBehavior(UseSynchronizationContext=false)]

[CallbackBehavior(UseSynchronizationContext=false)]

  這是由於服務操做會自動綁定服務的寄宿線程,爲了打破這種線程的親和性須要禁用同步上下文,不然服務就將是串行執行的,而且是採用同一個線程執行的,就沒有什麼「併發」可言了。(下P197)

  對於併發模式,WCF一樣提供了三個可選模式。

  Single模式表示一個實例上下文在某時刻只能處理單一請求,也就是說針對某個服務上下文的併發請求會串行執行。

  在這種模式下,當併發請求到來時,WCF會對實力上下文進行上鎖。

  Multiple模式表示一個實力上下文能夠同時處理多個請求。

  Reentrant(可重入)模式和Single相似,只能同時處理一個請求,然而一旦這個請求處理着一半就去回調客戶端了,那麼在客戶端響應以前,其餘的並行請求仍是能夠被它處理的。舉個不雅的例子,男人和老婆親熱着一半,老婆出去拿東西了,這時在外排隊的小三就能夠進來,等老婆回來了,須要先等小三出來,本身再進去……

  在這種模式下,若是須要服務端對客戶端進行回調,那麼要麼採用OneWay的形式回調,要麼就要把服務的併發模式設置爲非Single,不然會形成死鎖的異常,由於「小三」是會佔有「原配」的鎖的。(下P182)

  要讓服務支持併發,須要給服務打上服務行爲標籤,默認值是Single,一樣也能夠給CallbackBehavior標籤設置併發模式:

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single)]

  一樣,前面提到的實力上下文模式和併發模式也是有3*3=9種組合的。

  對於單調模式(PerCall),因爲每一個服務調用都使用一個實例上下文,因此根本不存在併發狀況,無需設置併發模式,可是對於同一個服務代理,若是須要並行發送請求,則須要手動開啓服務代理,不然服務是會串行調用的(P189)。

  對於會話模式(PerSession),併發將按照ConcurrencyMode所配置的方式進行處理。

  對於單例模式(Single),不論併發請求來自一個仍是多個客戶端,若ConcurrencyMode是Single則串行,是Multiple則並行,對Reentrant在回調發生時也是並行的(下P195)。

 

  11、限流

   爲了防止請求數量過多致使服務器資源耗盡,須要在消息接收和處理系統之間創建一道閘門來限制流量,能夠經過服務器端配置給服務添加行爲來進行流量控制:

<behavior name="throttlingBehavior">
    <serviceThrottling maxConcurrentCalls="16"
                        maxConcurrentInstances="116"
                        maxConcurrentSessions="100"/>
</behavior>

  三個屬性分別爲能處理的最大併發消息數量、服務實例上下文最大數量和最大併發會話數量,1六、11六、100分別是它們的默認值,在WCF4.0後,這些值是針對單個CPU而言的(下P204)。

相關文章
相關標籤/搜索