我們想對WCF具有一定了解的人都會知道:在客戶端通過服務調用進行服務調用過程中,服務代理應該及時關閉。但是如果服務的代理不等得到及時的關閉,到底具有怎樣的後果?什麼要關閉服務代理?在任何時候都需要關閉服務代理嗎?是否有一些例外呢?本篇文章將會圍繞著這些問題展開。
一、會話信道(Sessionful Channel) V.S. 數據報信道(Datagram Channel)
WCF通過信道棧實現了消息的編碼、傳輸及基於某些特殊功能對消息的特殊處理,而綁定對象是信道棧的締造者,不同的綁定類型創建出來的信道棧具有不同的特性。就對會話的支持來講,我們可以將信道分為以下兩種:
會話信道(Sessionful Channel):會話信道確保客戶端和服務端之間傳輸的消息能夠相互關聯,但是信道的錯誤(Fault)會影響後續的消息交換;
數據報信道(Datagram Channel):即使在同一個數據報信道中,每次消息的交換都是相互獨立,信道的錯誤也不會影響後續的消息交換。
對於絕大部分綁定類型(BasicHttpBinding除外),在默認的情況下創建的都是會話信道。對於WCF客戶端來說,如果進行基於會話信道的服務調用,有一些問題需要引起足夠的重視,如果使用不當,不但影響客戶端本身的服務調用,還會對服務處理請求的吞吐量造成很大的影響。
二、服務代理的關閉與並發會話(Concurrent Sessions)的限制
基於會話信道服務調用須要注意的第一個問題和WCF流量限制有關,為了使讀者對這個問題先有一個直觀認識,我們照例通過一個簡單的實驗來重現須要解決的問題。本例使用我們熟悉的計算服務例子,在服務寄宿的時候采用WsHttpBinding,下面是客戶端程序。
1: Binding binding = new WsHttpBinding(); EndpointAddress address = new EndpointAddress("http://127.0.0.1:9999/calculateservice"); ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>(binding, address); for (int i = 1; i <= 20; i++)
2: {
3: try
4: {
5: ICalculator calculator = channelFactory.CreateChannel();
6: Console.WriteLine("{3}: x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2), i);
7: }
8: catch (Exception ex) { Console.WriteLine("{0}t: {1}", i, ex.Message); }
9: }
輸出結果:
1: 1 : x + y = 3 when x = 1 and y = 2
2: 2 : x + y = 3 when x = 1 and y = 2
3: ......
4: 10: x + y = 3 when x = 1 and y = 2
5: 11: x + y = 3 when x = 1 and y = 212:請求通道在等待 00:00:59.9840000以後答復超時。增加傳遞給請求調用的超時值,或者增加綁定上的 SendTimeout 值。分配給此操作的時間可能是更長超時的一部分
從輸出的結果可以看出,雖然在代碼中我們通過一個for循環進行了20次服務調用,但是真正成功執行的僅僅有11次,第12次進行服務調用的時候,拋出Timeout異常。這種情況的出現源於WCF對並發會話數量的控制。說得具體點,WCF對一個ServiceHost所能處理的並發會話作了限制,在默認的情況下,允許的最大並發會話數量為10。
那麼細心的讀者馬上會問一個問題,既然默認的並發會話數量為10,為什麼上面的例子中,會有11次成功的並發服務調用呢?這是因為,服務端的信道監聽器允許一個額外的會話信道。在很多情況下,11個並發會話肯定是不能滿足具體的需求的,那麼是否可通過相應的配置根據具體的需求靈活指定一個合適的最大並發會話數量呢?答案是肯定的,服務允許的最大並發會話可以通過ServiceThrottlingBehavior服務行為的MaxConcurrentSessions屬性進行配置。在下面的配置中,將該值設為了20。
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <behaviors>
5: <serviceBehaviors>
6: <behavior name="highConcurrencyBehavior">
7: <serviceThrottling maxConcurrentSessions="20" />
8: </behavior>
9: </serviceBehaviors>
10: </behaviors>
11: ... ...
12: </system.serviceModel>
13: </configuration>
WCF對服務的並發會話的限制給WCF客戶端提出了一個要求,那就是在服務代理不再使用的情況下,應該及時將其關閉。基於服務代理對象的會話會隨著服務代理的關閉而關閉。服務端在處理客戶端請求的時候,如果當前並發的會話數量超過了所允許的范圍,後續的請求將會被放入等待隊列,以等待現有會話的結束。對於客戶端來說,服務調用在允許的超時時限(默認1分鐘)內還未接收到回復,則會拋出一個TimeoutException異常,如例子所表現的一樣。如果能夠及時地關閉服務代理對象,即使是2000次調用都沒有問題,如下所示:
1: Binding binding = new WSHttpBinding();
2: EndpointAddress address = new EndpointAddress("http://127.0.0.1:9999/calculateservice");
3: ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>(binding, address);
4: for (int i = 1; i <= 2000; i++)
5: {
6: ICalculator calculator = channelFactory.CreateChannel();
7: Console.WriteLine("{3}: x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2), i);
8: (calculator as ICommunicationObject).Close();
9: }
輸出結果:
1: 1 : x + y = 3 when x = 1 and y = 2
2: 2 : x + y = 3 when x = 1 and y = 2
3: ......
4: 1999: x + y = 3 when x = 1 and y = 2
5: 2000: x + y = 3 when x = 1 and y = 2
三、服務代理的關閉與否對數據報信道沒有影響
上面講的是對最大會話的限制,實際也可以說成是對最大會話信道的限制,那麼對於非會話信道是否也有此限制呢?實踐出真知,照例通過具體的例子來說明問題。我們知道綁定是信道的創建者,信道的特性通過組成綁定的元素(綁定元素)決定,所以信道對會話支持的特性也不例外。以上面例子使用的WsHttpBinding為例,只有WsHttpBinding的安全(Security)或可靠會話(Reliable Session)開啟的情況下,創建的信道才具有會話的特性,否則創建出來的信道是不能支持信道的。在默認的情況下,WsHttpBinding的安全模式(SecurityMode)為基於消息的安全,所以創建出來的信道自動被賦予了會話的特性。
為了驗證在非會話信道的情況下,WCF最大並發會話限制是否存在,我們對上面的代碼稍加修改,在創建WsHttpBinding的時候,將安全模式設為SecurityMode.None(當然,在進行服務寄宿的時候,WsHttpBinding也須要進行相同的設置)。通過最終輸出結果可以看出,MaxConcurrentSessions的限制不適合非會話邦定。
1: Binding binding = new WSHttpBinding(SecurityMode.None);
2: EndpointAddress address = new EndpointAddress("http://127.0.0.1:9999/calculateservice");
3: ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>(binding, address);
4: for (int i = 1; i <= 2000; i++)
5: {
6: ICalculator calculator = channelFactory.CreateChannel();
7: Console.WriteLine("{3}: x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2), i);
8: }
輸出結果:
1: 1 : x + y = 3 when x = 1 and y = 2
2: 2 : x + y = 3 when x = 1 and y = 2
3: ......
4: 1999: x + y = 3 when x = 1 and y = 2
5: 2000: x + y = 3 when x = 1 and y = 2