WCF中实例模式(InstanceContextMode)与会话模式(SessionMode)
InstanceContextMode为服务端实现服务契约类的实例模式,有三种类型,分别为:PerCall-每次服务操作调用创建一次,
调用完后进行销毁;PerSession-同一个会话期间创建一次,客户端代理第一次操作(IsInitiating = true)调用创建,
调用代理的Close方法销毁或者调用IsTerminating服务操作销毁;Single-服务只会创建一次,服务开始时创建,服务完
成时销毁
SessionMode是客户端代理与服务器之间的会话模式,同样也有三种类型:Allowed-允许会话、NotAllowed-不允许会话、Required-要求会话(需要有支持会话的Binding支持,WsHttpBinding、NetTcpBinding等)
本文是对不同实例模式下的不同会话模式进行的测试比较,代码来自attach的文章。
测试代码及配置文件说明
服务契约:
[ServiceContract] public interface ICalculator { [OperationContract(IsOneWay = true)] void Add(double x); [OperationContract] double GetResult(); }
服务契约实现:
[ServiceBehavior] class CalculatorService :appledou.Test.WCF.Contract.ICalculator, IDisposable { private double _result; #region ICalculator Members public void Add(double x) { Console.WriteLine("The Add method is invoked and the current SessionID is: {0}", OperationContext.Current.SessionId); this._result += x; } public double GetResult() { Console.WriteLine("The GetResult method is invoked and the current SessionID is: {0}\r\n", OperationContext.Current.SessionId); return this._result; } #endregion public CalculatorService() { Console.ForegroundColor = ConsoleColor.Blue; Console.WriteLine("Calculator object has been created"); Console.ResetColor(); }
#region IDisposable Members public void Dispose() { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("The IDisposable.Dispose method is invoked\r\n"); Console.ResetColor(); } #endregion }
Add方法的参数累加在类变量_result里面,最后通过GetResult返回,同时在每个方法里面输出当前的SessionID,在构造
函数和Dispose方法输出调用信息,方便观测服务端行为。
服务器端配置文件:
<system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="CalculatorBehavior"> <serviceMetadata httpGetEnabled="true"/> behavior> serviceBehaviors> behaviors> <bindings> <wsHttpBinding> <binding name="wsHttp"> <reliableSession enabled="true" inactivityTimeout="00:00:30"/> binding> wsHttpBinding> bindings> <services> <service name="appledou.Test.WCF.Service.CalculatorService" behaviorConfiguration="CalculatorBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:8888/"/> baseAddresses> host> <endpoint address="Calculator" binding="wsHttpBinding" bindingConfiguration="wsHttp" contract="appledou.Test.WCF.Contract.ICalculator"/> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> service> services> system.serviceModel>
Binding为支持会话的wsHttpBinding
客户端调用代码:
ChannelFactorychannelFactory = new ChannelFactory ("Calculator"); Console.WriteLine("Create a calculator proxy: proxy1"); ICalculator proxy1 = channelFactory.CreateChannel(); Console.WriteLine("Invocate proxy1.Add(1)"); proxy1.Add(1); Console.WriteLine("Invocate proxy1.Add(2)"); proxy1.Add(2); Console.WriteLine("The result return via proxy1.GetResult() is : {0}\r\n", proxy1.GetResult()); (proxy1 as ICommunicationObject).Close(); //--------------------------------- Console.WriteLine("Create a calculator proxy: proxy2"); ICalculator proxy2 = channelFactory.CreateChannel(); Console.WriteLine("Invocate proxy2.Add(1)"); proxy2.Add(1); Console.WriteLine("Invocate proxy2.Add(2)"); proxy2.Add(2); Console.WriteLine("The result return via proxy2.GetResult() is : {0}", proxy2.GetResult()); (proxy2 as ICommunicationObject).Close();
客户端配置文件:
<system.serviceModel> <client> <endpoint name="Calculator" address="http://localhost:8888/Calculator" binding="wsHttpBinding" bindingConfiguration="wsHttp" contract="appledou.Test.WCF.Contract.ICalculator"/> client> <bindings> <wsHttpBinding> <binding name="wsHttp"> <reliableSession enabled="true" inactivityTimeout="00:00:30"/> binding> wsHttpBinding> bindings> system.serviceModel>
PerCall下的会话模式
单调服务的一个最重要优势在于它能够节省资源,支持系统的可伸缩性。由于服务实例的生命周期只存在于一次调用期间,特别对于那些持有昂贵资源的服务实例而言,这种方式可以有效地提高系统性能。而且,销毁服务实例时,WCF不会断开与客户端(通过客户端的代理)的连接,这比创建实例与连接所消耗的资源要少得多(引用wayfarer的博客)
首先设置服务实例为PerCall
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class CalculatorService :appledou.Test.WCF.Contract.ICalculator, IDisposable
SessionMode为Required
设置服务契约
[ServiceContract(SessionMode = SessionMode.Required)] public interface ICalculator
客户端输出:
可以看到GetResult并没有返回前两次累加的值,因为实例模式是PerCall
服务端输出:
从服务端输出显示更能说明问题,每次方法调用首先创建服务实例(蓝色字体输出),调用完成后调用Dispose方法释放资源(黄色输出),当垃圾回收时销毁服务实例。同时还可以看到一个有趣的现象就是虽然每次调用都会创建新的实例,但是在同一个客户端代理关闭之前SessionID是一致的(前三次-proxy1的SessionID一致,后三次-proxy2的SessionID一致),正好应了销毁服务实例时,WCF不会断开与客户端(通过客户端的代理)的连接。
SessionMode为NotAllowed
设置服务契约
[ServiceContract(SessionMode = SessionMode.NotAllowed)] public interface ICalculator
客户端输出:
因为服务实例为PerCall,所以同Required的SessionMode并没有区别
而服务端则没有了SessionID,其它的都一样。
SessionMode为Allowed
SessionMode的Allowed和Required的区别就是,Allowed允许有Session,也就是说如果是支持Session的Binding那么效果就和Required一样,否则效果跟NoAllowed一样。而Required必须要求是支持Session的Binding
PerSession下的会话模式
PerSession从字面上就可以理解为,对于每一个客户端代理或者说是会话创建一个实例,代理第一次调用服务契约操作创建实例,当代理调用Close方法或者Terminating方法([OperationContract(IsTerminating = true)])结束会话并释放服务实例,同时服务实例可以被垃圾回收。
设置服务实例为PerSession
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
class CalculatorService :appledou.Test.WCF.Contract.ICalculator, IDisposable
SessionMode为Required
设置服务契约
[ServiceContract(SessionMode = SessionMode.Required)] public interface ICalculator
客户端输出:
可以看到在代理Close方法执行之前的GetResult方法返回了前两次Add累加的值:
Console.WriteLine("The result return via proxy1.GetResult() is : {0}\r\n", proxy1.GetResult()); (proxy1 as ICommunicationObject).Close();
服务端输出:
在服务端当客户端第一次调用Add方法的时候创建实例,而调用Close完成后调用Dispose释放资源。同时在这期间的
SessionID是一致的,说明他们在同一个会话中执行的。
SessionMode为NotAllowed
设置服务契约
[ServiceContract(SessionMode = SessionMode.NotAllowed)] public interface ICalculator
客户端输出:
服务端输出:
无论是服务端或者客户端都跟PerCall的NotAllowed一样,我想这也好理解。因为服务实例的生命期是PerSession,而服务契约又不允许Session,所以服务实例的生命期就是PerCall之间,并且没有SessionID。
可是这样的组合在什么情况下才会有呢?设想如果CalculatorService服务类又实现了一个服务契约并且SessionMode为Required或者Allowed,那么这个时候CalculatorService的InstanceContextMode为PerSession就有意义了,对于SessionMode为NotAllowed的服务契约会跟PerCall一样,但是对于SessionMode为Required或者Allowed的服务契约就是真正的PerSession了。如下图:
前三个方法调用,每次创建并且完成调用后释放对象,而后三次调用则在一个会话之内。这是因为同一个服务类CalculatorService
又实现了另一个服务契约ICalculatorV2,并且它要求SessionMode为Required,而原来的服务契约ICalculator的
SessionMode为NotAllowed:
[ServiceContract(SessionMode = SessionMode.Required)] public interface ICalculatorV2 { [OperationContract(IsOneWay = true, IsInitiating = true)] void Add2(double x); [OperationContract(IsTerminating = false)] double GetResult2(); }
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
class CalculatorService : appledou.Test.WCF.Contract.ICalculator, appledou.Test.WCF.Contract.ICalculatorV2, IDisposable
客户端调用代码:
ChannelFactorychannelFactory = new ChannelFactory ("Calculator"); Console.WriteLine("Create a calculator proxy of ICalculatorV1: proxy1"); ICalculator proxy1 = channelFactory.CreateChannel(); Console.WriteLine("Invocate proxy1.Add(1)"); proxy1.Add(1); Console.WriteLine("Invocate proxy1.Add(2)"); proxy1.Add(2); Console.WriteLine("The result return via proxy1.GetResult() is : {0}\r\n", proxy1.GetResult()); (proxy1 as ICommunicationObject).Close(); ChannelFactorychannelFactory2 = new ChannelFactory ("ICalculatorV2"); Console.WriteLine("Create a calculator proxy2 of ICalculatorV2: proxy2"); ICalculatorV2 proxy2 = channelFactory2.CreateChannel(); Console.WriteLine("Invocate proxy2.Add2(1)"); proxy2.Add2(1); Console.WriteLine("Invocate proxy2.Add2(2)"); proxy2.Add2(2); Console.WriteLine("The result return via proxy2.GetResult() is : {0}\r\n", proxy2.GetResult2()); (proxy2 as ICommunicationObject).Close();
客户端输出:
SessionMode为Allowed
同样道理在支持Session的Binding下面同Required一样,在不支持Session的Binding下面同NotAllowed一样。
Single下的会话模式
Single模式下的服务只创建一次,而且是在服务打开的时候,这个模式比较好理解,下面看看Single模式下SessionMode的不同情况
首先设置服务实例为Single
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
class CalculatorService :appledou.Test.WCF.Contract.ICalculator, IDisposable
SessionMode为Required
设置服务契约
[ServiceContract(SessionMode = SessionMode.Required)] public interface ICalculator
客户端输出:
可以看到在两次不同的代理调用后,服务器返回的值一直在累加,这说明虽然客户端代理不一样,但是服务端调用的是同一个实例。
服务端输出:
首先在服务打开后,Single类型的服务模型就被创建,其次 在同一个代理调用期间SessionID是一致的。
SessionMode为NotAllowed
设置服务契约
[ServiceContract(SessionMode = SessionMode.NotAllowed)] public interface ICalculator
客户端输出:
没什么变化,同Required模式下一样
服务端输出:
而服务端如NotAllowed所愿没有了SessionID,其它都一样
SessionMode为Allowed
这个就不用多说了
小结
本文是在WsHttpBinding的基础之上进行的测试,我想同样适用于其它支持Session的Binding,如NetTcpBinding。希望本文能帮助你理清InstanceContextMode和SessionMode之间不同取值的情况下对服务实例的影响。