WCF(8)之WCF中Session、實例管理詳解

一、引 言

   由前面幾篇博文我們知道,WCF是微軟基於SOA建立的一套在分佈式環境中各個相對獨立的應用進行交流(Communication)的框架,它實現了最新的基於WS-*規範。按照SOA的原則,相對獨自的業務邏輯以Service的形式進行封裝,調用者通過消息(Messaging)的方式來調用服務。對於承載某個業務功能實現的服務應該具有上下文(Context)無關性,意思就是說構造服務的操作(Operation)不應該綁定到具體的調用上下文,對於任何的調用,具有什麼的樣輸入就會對應怎樣的輸出。因爲SOA一個最大的目標是儘可能地實現重用,只有具有Context無關性,服務才能最大限度的重用。即從軟件架構角度理解爲,一個模塊只有儘可能的獨立,即具有上下文無關性,才能被最大限度的重用。軟件體系一直在強調低耦合也是這個道理。

  但是在某些場景下,我們卻希望系統爲我們創建一個Session來保留Client和Service的交互的狀態,如Asp.net中Session的機制一樣,WCF也提供了對Session的支持。下面就具體看看WCF中對Session的實現。

 

二、WCF中Session詳細介紹

 2.1 Asp.net的Session與WCF中的Session

  在WCF中,Session屬於Service Contract的範疇,並在Service Contract定義中通過SessionModel參數來實現。WCF中會話具有以下幾個重要的特徵:

  • Session都是由Client端顯示啓動和終止的。

  在WCF中Client通過創建的代理對象來和服務進行交互,在支持Session的默認情況下,Session是和具體的代理對象綁定在一起,當Client通過調用代理對象的某個方法來訪問服務時,Session就被初始化,直到代理的關閉,Session則被終止。我們可以通過兩種方式來關閉Proxy:一是調用ICommunicationObject.Close 方法,二是調用ClientBase<TChannel>.Close 方法 。我們也可以通過服務中的某個操作方法來初始化、或者終止Session,可以通過OperationContractAttribute的IsInitiating和IsTerminating參數來指定初始化和終止Session的Operation。

  • 在WCF會話期間,傳遞的消息按照它發送的順序被接收。

  • WCF並沒有爲Session的支持保存相關的狀態數據。

  講到Session,做過Asp.net開發的人,自然想到的就是Asp.net中的Session。它們只是名字一樣,在實現機制上有很大的不同。Asp.net中的Session具有以下特性:

  • Asp.net的Session總是由服務端啓動的,即在服務端進行初始化的。

  • Asp.net中的Session是無需,不能保證請求處理是有序的。

  • Asp.net是通過在服務端以某種方式保存State數據來實現對Session的支持,例如保存在Web Server端的內存中。

2.2 WCF中服務實例管理

  對於Client來說,它實際上不能和Service進行直接交互,它只能通過客戶端創建的Proxy來間接地和Service進行交互,然而真正的調用而是通過服務實例來進行的。我們把通過Client的調用來創建最終的服務實例過程稱作激活,在.NET Remoting中包括Singleton模式、SingleCall模式和客戶端激活方式,WCF中也有類似的服務激活方式:單調服務(PerCall)、會話服務(PerSession)和單例服務(Singleton)。

  • 單調服務(Percall):爲每個客戶端請求分配一個新的服務實例。類似.NET Remoting中的SingleCall模式

  • 會話服務(Persession):在會話期間,爲每次客戶端請求共享一個服務實例,類似.NET Remoting中的客戶端激活模式。

  • 單例服務(Singleton):所有客戶端請求都共享一個相同的服務實例,類似於.NET Remoting的Singleton模式。但它的激活方式需要注意一點:當爲對於的服務類型進行Host的時候,與之對應的服務實例就被創建出來,之後所有的服務調用都由這個服務實例進行處理。

  WCF中服務激活的默認方式是PerSession,但不是所有的Bingding都支持Session,比如BasicHttpBinding就不支持Session。你也可以通過下面的方式使ServiceContract不支持Session.

[ServiceContract(SessionMode = SessionMode.NotAllowed)]

  下面分別介紹下這三種激活方式的實現。

 

三、WCF中實例管理的實現

   WCF中服務激活的默認是PerSession的方式,下面就看看PerSession的實現方式。我們還是按照前面幾篇博文的方式來實現使用PerSession方式的WCF服務程序。

  第一步:自然是實現我們的WCF契約和契約的服務實現。具體的實現代碼如下所示: 

1 // 服務契約的定義
2 [ServiceContract]
3 public interface ICalculator
4 {
5 [OperationContract(IsOneWay = true)]
6 void Increase();
7
8 [OperationContract]
9 int GetResult();
10 }
11
12 // 契約的實現
13 public class CalculatorService : ICalculator, IDisposable
14 {
15 private int _nCount = 0;
16
17 public CalculatorService()
18 {
19 Console.WriteLine("CalulatorService object has been created");
20 }
21
22 // 爲了看出服務實例的釋放情況
23 public void Dispose()
24 {
25 Console.WriteLine("CalulatorService object has been Disposed");
26 }
27
28 #region ICalulator Members
29 public void Increase()
30 {
31 // 輸出Session ID
32 Console.WriteLine("The Add method is invoked and the current session ID is: {0}", OperationContext.Current.SessionId);
33 this._nCount++;
34 }
35
36 public int GetResult()
37 {
38 Console.WriteLine("The GetResult method is invoked and the current session ID is: {0}", OperationContext.Current.SessionId);
39 return this._nCount;
40 }
41 #endregion
42 }

  爲了讓大家對服務對象的創建和釋放有一個直觀的認識,我特意對服務類實現了構造函數和IDisposable接口,同時在每個操作中輸出當前的Session ID。

  第二步:實現服務宿主程序。這裏還是採用控制檯程序作爲服務宿主程序,具體的實現代碼如下所示:

1 // 服務宿主程序
2 class Program
3 {
4 static void Main(string[] args)
5 {
6 using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
7 {
8 host.Opened += delegate
9 {
10 Console.WriteLine("The Calculator Service has been started, begun to listen request...");
11 };
12
13 host.Open();
14 Console.ReadLine();
15 }
16 }
17 }

  對應的配置文件爲:

<!--服務宿主的配置文件-->
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name ="CalculatorBehavior">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="WCFContractAndService.CalculatorService" behaviorConfiguration="CalculatorBehavior">
<endpoint address="" binding="basicHttpBinding" contract="WCFContractAndService.ICalculator"/>
<host>
<baseAddresses>
<add baseAddress="http://localhost:9003/CalculatorPerSession"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>

  第三步:實現完了服務宿主程序,接下來自然是實現客戶端程序來訪問服務操作。這裏的客戶端也是控制檯程序,具體的實現代碼如下所示:

1 // 客戶端程序實現
2 class Program
3 {
4 static void Main(string[] args)
5 {
6 // Use ChannelFactory<ICalculator> to create WCF Service proxy
7 ChannelFactory<ICalculator> calculatorChannelFactory = new ChannelFactory<ICalculator>("HttpEndPoint");
8 Console.WriteLine("Create a calculator proxy :proxy1");
9 ICalculator proxy1 = calculatorChannelFactory.CreateChannel();
10 Console.WriteLine("Invoke proxy1.Increate() method");
11 proxy1.Increase();
12 Console.WriteLine("Invoke proxy1.Increate() method again");
13 proxy1.Increase();
14 Console.WriteLine("The result return via proxy1.GetResult() is: {0}", proxy1.GetResult());
15
16 Console.WriteLine("Create another calculator proxy: proxy2");
17 ICalculator proxy2 = calculatorChannelFactory.CreateChannel();
18 Console.WriteLine("Invoke proxy2.Increate() method");
19 proxy2.Increase();
20 Console.WriteLine("Invoke proxy2.Increate() method again");
21 proxy2.Increase();
22 Console.WriteLine("The result return via proxy2.GetResult() is: {0}", proxy2.GetResult());
23
24 Console.ReadLine();
25 }
26 }

  客戶端對應的配置文件內容如下所示:

<!--客戶端配置文件-->
<configuration>
<system.serviceModel>
<client>
<endpoint address="http://localhost:9003/CalculatorPerSession"
binding="basicHttpBinding"
contract="WCFContractAndService.ICalculator" name="HttpEndPoint"/>
</client>
</system.serviceModel>
</configuration>

  經過上面三步,我們就完成了PerSession方式的WCF程序了,下面看看該程序的運行結果。

  首先,以管理員權限運行服務寄宿程序,運行成功後,你將看到如下圖所示的畫面:

  接下來,運行客戶端對服務操作進行調用,運行成功後,你將看到服務宿主的輸出和客戶端的輸出情況如下圖所示:

  從客戶端的運行結果可以看出,雖然我們兩次調用了Increase方法來增加_nCount的值,但是最終的運行結果仍然是0。這樣的運行結果好像與我們之前所說的WCF默認Session支持矛盾,因爲如果WCF默認的方式PerSession的話,則服務實例是和Proxy綁定在一起,當Proxy調用任何一個操作的時候Session開始,從此Session將會與Proxy具有一樣的生命週期。按照這個描述,客戶端運行的結果應該是2而不是0。這裏,我只能說運行結果並沒有錯,因爲有圖有真相嘛,那到底是什麼原因導致客戶端獲得_nCount值是0呢?其實在前面已經講到過,並不是所有的綁定都是支持Session的,上面程序的實現我們使用的basicHttpBinding,而basicHttpBinding是不支持Session方式的,所以WCF會採用PerCall的方式創建Service Instance,所以在服務端中對於每一個Proxy都有3個對象被創建,兩個是對Increase方法的調用會導致服務實例的激活,另一個是對GetResult方法的調用導致服務實例的激活。因爲是PerCall方式,所以每次調用完之後,就會對服務實例進行釋放,所以對應的就有3行服務對象釋放輸出。並且由於使用的是不支持Session的binding,所以Session ID的輸出也爲null。所以,上面WCF程序其實是PerCall方式的實現。

  既然,上面的運行結果是由於使用了不支持Session的basicHttpBinding導致的,下面看看使用一個支持Session的Binding:wsHttpBinding來看看運行結果是怎樣的,這裏的修改很簡單,只需要把宿主和客戶端的配置文件把綁定類型修改爲wsHttpBinding就可以了。

<!--客戶端配置文件-->
<configuration>
<system.serviceModel>
<client>
<endpoint address="http://localhost:9003/CalculatorPerSession"
binding="wsHttpBinding"
contract="WCFContractAndService.ICalculator" name="HttpEndPoint"/>
</client>
</system.serviceModel>
</configuration>
<!--服務宿主的配置文件-->
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name ="CalculatorBehavior">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="WCFContractAndService.CalculatorService" behaviorConfiguration="CalculatorBehavior">
<endpoint address="" binding="wsHttpBinding" contract="WCFContractAndService.ICalculator"/>
<host>
<baseAddresses>
<add baseAddress="http://localhost:9003/CalculatorPerSession"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>

  現在我們再運行下上面的程序來看看此時的執行結果,具體的運行結果如下圖所示:

  從上面的運行結果可以看出,此時兩個Proxy的運行結果都是2,可以看出此時服務激活方式採用的是PerSession方式。此時對於服務端就只有兩個服務實例被創建了,並且對於每個服務實例具有相同的Session ID。 另外由於Client的Proxy還依然存在,服務實例也不會被回收掉,從上面服務端運行的結果也可以證實這點,因爲運行結果中沒有對象唄Disposable的輸出。你可以在客戶端顯式調用ICommunicationObject.Close方法來顯式關閉掉Proxy,在客戶端添加對Proxy的顯示關閉代碼,此時客戶端的代碼修改爲如下所示:

1 // 客戶端程序實現
2 class Program
3 {
4 static void Main(string[] args)
5 {
6 // Use ChannelFactory<ICalculator> to create WCF Service proxy
7 ChannelFactory<ICalculator> calculatorChannelFactory = new ChannelFactory<ICalculator>("HttpEndPoint");
8 Console.WriteLine("Create a calculator proxy :proxy1");
9 ICalculator proxy1 = calculatorChannelFactory.CreateChannel();
10 Console.WriteLine("Invoke proxy1.Increate() method");
11 proxy1.Increase();
12 Console.WriteLine("Invoke proxy1.Increate() method again");
13 proxy1.Increase();
14 Console.WriteLine("The result return via proxy1.GetResult() is: {0}", proxy1.GetResult());
15 (proxy1 as ICommunicationObject).Close(); // 顯示關閉Proxy
16
17 Console.WriteLine("Create another calculator proxy: proxy2");
18 ICalculator proxy2 = calculatorChannelFactory.CreateChannel();
19 Console.WriteLine("Invoke proxy2.Increate() method");
20 proxy2.Increase();
21 Console.WriteLine("Invoke proxy2.Increate() method again");
22 proxy2.Increase();
23 Console.WriteLine("The result return via proxy2.GetResult() is: {0}", proxy2.GetResult());
24 (proxy2 as ICommunicationObject).Close();
25
26 Console.ReadLine();
27 }
28 }

  此時,服務對象的Dispose()方法將會調用,此時服務端的運行結果如下圖所示:

  上面演示了默認支持Session的情況,下面我們修改服務契約使之不支持Session,此時只需要知道ServiceContract的SessionMode爲NotAllowed即可。

[ServiceContract(SessionMode= SessionMode.NotAllowed)] // 是服務契約不支持Session
public interface ICalculator
{
[OperationContract(IsOneWay = true)]
void Increase();

[OperationContract]
int GetResult();
}

  此時,由於服務契約不支持Session,此時服務激活方式採用的仍然是PerCall。運行結果與前面採用不支持Session的綁定的運行結果一樣,這裏就不一一貼圖了。

  除了通過顯式修改ServiceContract的SessionMode來使服務契約支持或不支持Session外,還可以定製操作對Session的支持。定製操作對Session的支持可以通過OperationContract的IsInitiating和InTerminating屬性設置。

1 // 服務契約的定義
2 [ServiceContract(SessionMode= SessionMode.Required)] // 顯式使服務契約支持Session
3 public interface ICalculator
4 {
5 // IsInitiating:該值指示方法是否實現可在服務器上啓動會話(如果存在會話)的操作,默認值是true
6 // IsTerminating:獲取或設置一個值,該值指示服務操作在發送答覆消息(如果存在)後,是否會導致服務器關閉會話,默認值是false
7 [OperationContract(IsOneWay = true, IsInitiating =true, IsTerminating=false )]
8 void Increase();
9
10 [OperationContract(IsInitiating = true, IsTerminating = true)]
11 int GetResult();
12 }

  在上面代碼中,對兩個操作都設置InInitiating的屬性爲true,意味着調用這兩個操作都會啓動會話,而把GetResult操作的IsTerminating設置爲true,意味着調用完這個操作後,會導致服務關閉掉會話,因爲在Session方式下,Proxy與Session有一致的生命週期,所以關閉Session也就是關閉proxy對象,所以如果後面再對proxy對象的任何一個方法進行調用將會導致異常,下面代碼即演示了這種情況。

1 // 客戶端程序實現
2 class Program
3 {
4 static void Main(string[] args)
5 {
6 // Use ChannelFactory<ICalculator> to create WCF Service proxy
7 ChannelFactory<ICalculator> calculatorChannelFactory = new ChannelFactory<ICalculator>("HttpEndPoint");
8 Console.WriteLine("Create a calculator proxy :proxy1");
9 ICalculator proxy1 = calculatorChannelFactory.CreateChannel();
10 Console.WriteLine("Invoke proxy1.Increate() method");
11 proxy1.Increase();
12 Console.WriteLine("Invoke proxy1.Increate() method again");
13 proxy1.Increase();
14 Console.WriteLine("The result return via proxy1.GetResult() is: {0}", proxy1.GetResult());
15 try
16 {
17 proxy1.Increase(); // session關閉後對proxy1.Increase方法調用將會導致異常
18 }
19 catch (Exception ex) // 異常捕獲
20 {
21 Console.WriteLine("在Session關閉後調用Increase方法失敗,錯誤信息爲:{0}", ex.Message);
22 }

23
24 Console.WriteLine("Create another calculator proxy: proxy2");
25 ICalculator proxy2 = calculatorChannelFactory.CreateChannel();
26 Console.WriteLine("Invoke proxy2.Increate() method");
27 proxy2.Increase();
28 Console.WriteLine("Invoke proxy2.Increate() method again");
29 proxy2.Increase();
30 Console.WriteLine("The result return via proxy2.GetResult() is: {0}", proxy2.GetResult());
31
32 Console.ReadLine();
33 }
34 }

  此時運行結果也驗證我們上面的分析,客戶端和服務端的運行結果如下圖所示:

  上面演示了PerSession和PerCall的兩種服務對象激活方式,下面看看Single的激活方式運行的結果。首先通過ServiceBehavior的InstanceContextMode屬性顯式指定激活方式爲Single,由於ServiceBehaviorAttribute特性只能應用於類上,所以把該特性應用於CalculatorService類上,此時服務實現的代碼如下所示:

// 契約的實現
// ServiceBehavior屬性只能應用在類上
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] // 顯示指定PerSingle方式
public class CalculatorService : ICalculator, IDisposable
{
private int _nCount = 0;

public CalculatorService()
{
Console.WriteLine("CalulatorService object has been created");
}

// 爲了看出服務實例的釋放情況
public void Dispose()
{
Console.WriteLine("CalulatorService object has been Disposed");
}

#region ICalulator Members
public void Increase()
{
// 輸出Session ID
Console.WriteLine("The Add method is invoked and the current session ID is: {0}", OperationContext.Current.SessionId);
this._nCount++;
}

public int GetResult()
{
Console.WriteLine("The GetResult method is invoked and the current session ID is: {0}", OperationContext.Current.SessionId);
return this._nCount;
}
#endregion
}

  此時運行服務宿主的輸出結果如下圖所示:

  從運行結果可以看出,對於Single方式,服務實例在服務類型被寄宿的時候就已經創建了,對於PerCall和PerSession方式而是在通過Proxy調用相應的服務操作之後,服務實例纔開始創建的。下面運行客戶端程序,你將看到如下圖所示的運行結果:

  此時,第二個Proxy返回的結果是4而不是2,這是因爲採用Single方式只存在一個服務實例,所有的調用狀態都將保留,所以_nCount的值在原來的基礎上繼續累加。

 

四、總結

  到這裏,本文的分享就結束了,本文主要分享了WCF中實例管理的實現。從WCF的實例實現可以看出,WCF實例實現是借鑑了.NET Remoting中實例實現,然後分別分享了服務實例三種激活方式在WCF中的實現,並通過對運行結果進行對比來讓大家理解它們之間的區別。

 

轉自:https://www.cnblogs.com/zhili/p/MSMQ.html,作者:Learning hard。

如有侵權,請聯繫我刪除!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章