.Net Remoting的激活方式也有三種:SingleTon模式、SingleCall模式、客戶端激活方式,WCF服務實例激活類型包括三種方式:單調服務(Call Service),會話服務(Sessionful Service),單例服務(Singleton Service).他們之間有什麼聯系。WCF服務激活類型的優勢和缺點,以及如何在項目裡編程開發和設置服務實例。全文分為5個部分,首先一次介紹單調服務(Call Service),會話服務(Sessionful Service),單例服務(Singleton Service)的相關概念,優勢和缺點,其次是示例代碼分析和講解部分,最後是全文的總結部分。結構如下:【1】單調服務(Call Service)【2】會話服務(Sessionful Service)【3】單例服務(Singleton Service)【4】示例代碼分析【5】總結。最後會上傳本文的代碼。
【引言】:
在WCF分布式開發必備知識(2):.Net Remoting這篇文章裡我已經介紹過了Net Remoting相關的概念,其中也包括Net Remoting的激活方式:SingleTon模式、SingleCall模式、客戶端激活方式。其實WCF服務的激活方式也與此相似。服務激活方式也是WCF借鑒Net Remoting的一個明顯的例子。Net Remoting相關的概念大家可以查閱WCF分布式開發必備知識(2):.Net Remoting這篇文章。 下面我們就來詳細的介紹WCF服務激活類型相關的知識點。首先來介紹的是單調服務。
WCF支持三種實例激活的類型:
1>.單調服務(Per-Call Service):每次的客戶端請求分配一個新的服務實例。類似於Net Remoting的SingleCall模式;
2>.會話服務(Sessionful Service):則為每次客戶端連接分配一個服務實例。類似於Net Remoting的客戶端激活模式;
3>.單例服務(Singleton Service):所有的客戶端會為所有的連接和激活對象共享一個相同的服務實例。類似於Net Remoting的SingleTon模式。
這裡的服務激活模式是由我們定義的服務的上下文模式InstanceContextMode
屬性來配置的,其代碼如下:
public enum InstanceContextMode
{
PerSession,
PerCall,
Single
}
【1】單調服務(Call Service):
【1.1】基本概念
單調服務(Per-Call Service):每次的客戶端請求分配一個新的服務實例。服務實例的生存周期緊緊限制於一次調用的開始與結束之間。客戶端的每次請求都會產生新的服務實例來響應這個調用。類似於Net Remoting的SingleCall模式。 執行步驟如下:
1.客戶端調用代理,代理將調用轉發給服務。
2.WCF創建一個服務實例,然後調用服務實例的方法。
3.當方法調用返回時,如果對象實現了IDisposable接口,WCF將調用IDisposable.Dispose()方法。
4.客戶端調用代理,代理將調用轉發給服務。
5.WCF創建一個對象,然後調用對象的方法。
單調服務的實例化模型圖:
【1.2】開發配置:
單調服務開發配置十分簡單,我們使用[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]配置服務屬性完成。這樣的WCF服務模式為單調模式,WCF框架對自動更具設置的屬性來決定具體的服務激活類型。代碼如下所示:
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]
public class WCFServicePerCall : IWCFService,IDisposable
{
}
【1.3】注意:
(1)[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]特性只能應用到類上。
(2) 如果使用了昂貴的資源,如數據庫連接等非托管資源,繼承IDisposable接口,//實現接口定義的方法Dispose()方法顯示釋放資源。但是也有弊端,頻繁地創建與銷毀實例,仍然會對性能造成一定的影響。
(3)對於WCF服務而言,單調服務可以算是最佳的實例激活模式。 單調服務的一個最重要優勢在於它能夠節省資源,支持系統的可伸縮性。另外在事務編程與隊列服務中優勢更為明顯,在事務編程中新建服務實例,減少實例狀態的同步;而消息隊列,單調服務能夠建立服務實例與隊列消息之間的簡單映射。詳細信息會在後續文章中介紹。
【2】會話服務(Sessionful Service):
【2.1】基本概念:
會話服務(Sessionful Service):則為每次客戶端連接分配一個服務實例。類似於Net Remoting的客戶端激活模式。為每個客戶端創建一個專門的服務實例。只要會話沒有結束,該實例就不會被銷毀。 對於會話服務而言,是一個客戶端代理對應一個服務實例。也就是說,會話服務中的服務是與代理相對應的,而不是對應於一個客戶端。
【2.2】配置開發:
服務實例的默認激活方式為會話服務模式。我們也可以顯示配置會話服務的方式,使用[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)],具體代碼如下所示:
//3.服務類.會話服務
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class WCFServicePerSession : IWCFService
{
}
服務配置[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]之後,需要在服務契約一級配置[ServiceContract(SessionMode=SessionMode.Allowed)],服務元數據將包含SessionMode值。客戶端的WCF反序列化後會包含此信息,來確定服務是否使用了會話模式,SessionMode為枚舉類型:
public enum SessionMode
{
Allowed,
Required,
NotAllowed
}
不是所有的綁定協議都支持會話傳輸模式,TCP協議為傳輸控制協議,會與客戶端維護一個連接。而HTTP為無連接狀態,我們無法保證其與客戶端的會話連接。
【2.3】注意:
(1)會話服務存在可伸縮性的問題。由於每個客戶端都需要維護一個會話,需要占用較多的資源來保存服務會話狀態。如果存在多個獨立的客戶端,則創建專門的服務實例的代價太大。
(2)WCF服務綁定協議與會話特性之間的關系見下表:
(3) 應該避免將單調服務與會話契約混合定義在相同的會話服務類型中,會話應該保證是可靠的,一個實現了會話契約的服務,它包含的所有終結點所公開的契約都應該使用支持可靠傳輸會話的綁定。
(4) InactivityTimeout可以配置一個新的空閒超時值,服務實例空閒時間超過這個范圍時候就會終止會話。InactivityTimeout屬性的默認值為10分鐘。不能將該值設置為小於或等於0的值,否則會拋出ArgumentOutOfRangeException異常。
【3】單例服務(Singleton Service):
【3.1】基本概念:
設計模式中最簡單和容易理解的就是單例(單件)模式(SingleTon),單例服務(Singleton Service)也是一種單件模式的實踐應用的例子。單例服務(Singleton Service)就是針對所有客戶端而言,都只有一個服務實例。單例服務的生存期是不受GC管理,不會終止,只有在關閉宿主時,才會被釋放。創建宿主時,單例服務的實例就會被創建(這個可以再托管宿主的監控狀態信息中得到證實,宿主運行時候,單例服務的已經顯示實例化完畢,而單調服務和會話服務實例尚未啟動),並且只能被創建一次,一直運行下去,有且僅有一個服務實例來響應客戶端服務調用的請求。
【3.2】配置與開發:
服務實例的單調激活模式可以通過[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]配置完成,具體的代碼如下:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class WCFServiceSingleTon : IWCFService
{
}
【3.3】注意:
(1)單例服務同一時間內只能相應一個客戶端請求。因此在系統的吞吐量、相應效率、系統服務性能上都存在嚴重的瓶頸。
(2)客戶端不需要維護會話狀態,也不需要支持特定的綁定協議。服務端只有一個服務實例。即使關閉了代理,也不會終止單例服務。
(3)另外單例服務狀態同步共享十分困難,可重用性和可伸縮性較差。除非是在特殊情況,應盡量避免使用單例服務。
【4】示例代碼分析:
下面我們來介紹本次的示例代碼,這裡我們分別定義了三種激活類型的服務類:單調服務(Per-Call Service),會話服務(Sessionful Service),單例服務(Singleton Service),托管宿主分別進行托管,這裡為了測試,我們使用的綁定協議也是TCP方式,其他的協議這裡沒做具體的實現,有興趣的朋友可以自己擴展修改代碼,進行測試。
【4.1】服務端:
定義了一個服務契約,一個操作SayHello(),具體的服務類型定義和激活類型配置如下:
//此例定義了一個服務契約,三種服務分別為單調服務、會話服務、單例服務或單件服務
namespace WCFService
{
//1.服務契約
[ServiceContract(SessionMode=SessionMode.Allowed, Namespace = "http://www.cnblogs.com/frank_xl/")]
public interface IWCFService
{
//操作契約
[OperationContract]
void SayHello();
}
//2.服務類.單調服務
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]
public class WCFServicePerCall : IWCFService,IDisposable
{
//服務實例計數
private int mCcount =0;
//構造函數
public WCFServicePerCall()
{
Console.WriteLine("WCFServicePerCall Instance is Created ");
}
//實現接口定義的方法
public void SayHello()
{
mCcount++;
Console.WriteLine("WCFServicePerCall Instance Count is: {0} ",mCcount);
}
//實現接口定義的方法Dispose
public void Dispose()
{
Console.WriteLine("WCFServicePerCall Instance is disposed ");
}
}
//3.服務類.會話服務
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class WCFServicePerSession : IWCFService
{
//服務實例計數
private int mCcount = 0;
//構造函數
public WCFServicePerSession()
{
Console.WriteLine("WCFServicePerSession Instance is Created ");
}
//實現接口定義的方法
public void SayHello()
{
mCcount++;
Console.WriteLine("WCFServicePerSession Instance Count is: {0} ", mCcount);
}
//實現接口定義的方法Dispose
public void Dispose()
{
Console.WriteLine("WCFServicePerCall Instance is disposed ");
}
}
//4.服務類.單例服務
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class WCFServiceSingleTon : IWCFService
{
//服務實例計數
private int mCcount = 0;
//構造函數
public WCFServiceSingleTon()
{
Console.WriteLine("WCFServiceSingleTon Instance is Created ");
}
//實現接口定義的方法
public void SayHello()
{
mCcount++;
Console.WriteLine("WCFServiceSingleTon Instance Count is: {0} ", mCcount);
}
//實現接口定義的方法Dispose
public void Dispose()
{
Console.WriteLine("WCFServicePerCall Instance is disposed ");
}
}
}
【4.2】宿主:
針對三種激活類型的服務分別定義了三個宿主實例:hostWCFServicePerCall、hostWCFServicePerSession、hostWCFServiceSingleTon。測試完畢,關閉宿主。代碼具體如下:
//采用自托管方式,也可以是IIS、WAS,Windows服務等用戶自定義程序托管服務
public class WCFHost
{
static void Main(string[] args)
{
//1.單調服務WCFServicePerCall
ServiceHost hostWCFServicePerCall = new ServiceHost(typeof(WCFService.WCFServicePerCall));
//判斷是否以及打開連接,如果尚未打開,就打開偵聽端口
if (hostWCFServicePerCall.State != CommunicationState.Opening)
hostWCFServicePerCall.Open();
//顯示運行狀態
Console.WriteLine("WCFServicePerCall Host is runing! and state is {0}", hostWCFServicePerCall.State);
//2.會話服務WCFServicePerSession
ServiceHost hostWCFServicePerSession = new ServiceHost(typeof(WCFService.WCFServicePerSession));
//判斷是否以及打開連接,如果尚未打開,就打開偵聽端口
if (hostWCFServicePerSession.State != CommunicationState.Opening)
hostWCFServicePerSession.Open();
//顯示運行狀態
Console.WriteLine("WCFServicePerSession Host is runing! and state is {0}", hostWCFServicePerSession.State);
//3.單例服務WCFServiceSingleTon
ServiceHost hostWCFServiceSingleTon = new ServiceHost(typeof(WCFService.WCFServiceSingleTon));
//判斷是否以及打開連接,如果尚未打開,就打開偵聽端口
if (hostWCFServiceSingleTon.State != CommunicationState.Opening)
hostWCFServiceSingleTon.Open();
//顯示運行狀態
Console.WriteLine("WCFServiceSingleTon Host is runing! and state is {0}", hostWCFServiceSingleTon.State);
//等待輸入即停止服務
Console.Read();
//4 Close Host
hostWCFServicePerCall.Close();
hostWCFServicePerSession.Close();
hostWCFServiceSingleTon.Close();
}
綁定協議這裡使用的是TCP,三個服務分別配置了服務的終結點,包括契約、地址、綁定。元數據交換節點也進行了配置。具體配置文件如下:
<service behaviorConfiguration="WCFService.WCFServiceBehavior" name="WCFService.WCFServicePerCall">
<endpoint
address="net.tcp://localhost:9001/WCFServicePerCall"
binding="netTcpBinding"
contract="WCFService.IWCFService">
</endpoint>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:9001/"/>
</baseAddresses>
</host>
</service>
<service behaviorConfiguration="WCFService.WCFServiceBehavior" name="WCFService.WCFServicePerSession">
<endpoint
address="net.tcp://localhost:9002/WCFServicePerSession"
binding="netTcpBinding"
contract="WCFService.IWCFService">
</endpoint>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:9002/"/>
</baseAddresses>
</host>
</service>
<service behaviorConfiguration="WCFService.WCFServiceBehavior" name="WCFService.WCFServiceSingleTon">
<endpoint
address="net.tcp://localhost:9003/WCFServiceSingleTon"
binding="netTcpBinding"
contract="WCFService.IWCFService">
</endpoint>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:9003/"/>
</baseAddresses>
</host>
</service>
</services>
【4.3】客戶端:
運行宿主,添加服務引用,反序列化服務元數據為本地代碼。完成以後添加測試的代碼。每種服務激活類型創建2個代理實例,分別調用2次服務,測試不同的服務激活類型設置對服務實例的影響。我們來觀察服務實例化的次數。具體代碼如下:
////////////////////////////////////////////單調服務//////////////////////////////////////////////////
#region //1.單調服務代理1 實例化,每次調用操作,會創建不同的服務實例
WCFServicePerCall.WCFServicePerCallClient WCFServicePerCallProxy1 = new WCFServicePerCall.WCFServicePerCallClient();
//調用2次服務
for (int i = 0; i < 2;i++ )
{
WCFServicePerCallProxy1.SayHello();
}
//關閉服務代理
WCFServicePerCallProxy1.Close();
WCFServicePerCall.WCFServicePerCallClient WCFServicePerCallProxy2 = new WCFServicePerCall.WCFServicePerCallClient();
//調用2次服務
for (int i = 0; i < 2; i++)
{
WCFServicePerCallProxy2.SayHello();
}
//關閉服務代理
WCFServicePerCallProxy2.Close();
#endregion
////////////////////////////////////////會話服務////////////////////////////////////////////////////////
#region//2.會話服務代理 實例化,一個客戶端代理對應一個服務實例
WCFServicePerSession.WCFServicePerSessionClient WCFServicePerSessionProxy1 = new WCFServicePerSession.WCFServicePerSessionClient();
//調用2次服務
for (int i = 0; i < 2; i++)
{
WCFServicePerSessionProxy1.SayHello();
}
//關閉服務代理
WCFServicePerSessionProxy1.Close();
WCFServicePerSession.WCFServicePerSessionClient WCFServicePerSessionProxy2 = new WCFServicePerSession.WCFServicePerSessionClient();
//調用2次服務
for (int i = 0; i < 2; i++)
{
WCFServicePerSessionProxy2.SayHello();
}
//關閉服務代理
WCFServicePerSessionProxy2.Close();
#endregion
////////////////////////////////////////////單例服務//////////////////////////////////////////////////
#region//2.單例服務代理 實例化,也叫單件模式。所有的服務只有一個服務實例
WCFServiceSingleTon.WCFServiceSingleTonClient WCFServiceSingleTonProxy1 = new WCFServiceSingleTon.WCFServiceSingleTonClient();
//調用2次服務
for (int i = 0; i < 2; i++)
{
WCFServiceSingleTonProxy1.SayHello();
}
WCFServiceSingleTonProxy1.Close();
WCFServiceSingleTon.WCFServiceSingleTonClient WCFServiceSingleTonProxy2 = new WCFServiceSingleTon.WCFServiceSingleTonClient();
//調用2次服務
for (int i = 0; i < 2; i++)
{
WCFServiceSingleTonProxy2.SayHello();
}
WCFServiceSingleTonProxy2.Close();
#endregion
//4.For Debugging
Console.WriteLine("Press any key to continue");
Console.Read();
【4.4】運行結果:
啟動托管宿主,運行客戶端進行測試,監控服務輸出信息如下:
【5】總結:
(1)單調服務每次都重新創建服務的實例,操作完成以後,釋放服務對象,每次想用用戶操作請求的服務實例不同。
(2)會話服務針對每次會話創建一個特定的服務實例對象,在一次會話中的所有請求由一個服務對象相應。我們的服務調用計數也在會話期間隨客戶端調用的次數增加,上圖可見。
(3)單例服務在宿主創建時就進行了實例化。他和會話和調用次數沒有關系,所有的客戶單服務調用操作均有同一個服務實例來響應。客戶單調用的次數越多,服務端實際調用次數就會隨之增加。上圖可見。
(4)另外我們也可以編程或者配置系統的最大並發調用數、最大並發會話數、最大並發實例數,來控制和管理服務實例的負荷和流量。
本文配套源碼