[有兴趣阅读本文的请从头至尾阅读,有兴趣帮助我解答疑问的请从尾至头读(红色部分),万分感谢!]
我们很容易理解在旧有编程模型中关于类实例的内容。设计模式中Singleton也就是在描述着档子事。但基于WCF并非适合于以上场景,Service与Client之间要保持良好的Instance模型则需要依靠很多其他机制。
Programming WCF Service Chapter4 对此进行了细致的描述。(更多细节请自行阅读~)
WCF支持三种类型的Instance管理:
1、pre-call services:每个客户端请求对应一个instance
2、Sessionful services:每个客户端连接对应一个instance
3、Singleton services:所有客户端共享一个instance
利用Behaviors可以解决这方面的问题(还有一些其他基于“服务端”的其他方面的问题可以通过使用behaviors来解决)。
注:客户端是不知道服务端设置了什么样的behaviors的。
VS2008MSDN:ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/fxref_system.servicemodel/html/88efb135-d425-e5b1-57d6-01a67158c1a5.htm
Apply the ServiceBehaviorAttribute attribute to a service implementation to specify service-wide execution behavior. (To specify execution behavior at the method level, use the OperationBehaviorAttribute attribute.) This attribute can be applied only to service implementations.
ServiceBehaviorAttribute:仅应用于服务实现。
OperationBehaviorAttribute:用于方法级别。
//瞧这里什么属性都没有
public interface IMyContract
{}
//而是设置在了具体服务实现上
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MyContract : IMyContract,IDisposable
{}
设置instance模式类型由ServiceBehaviorAttribute的属性InstanceContextMode进行设置,默认值为PerSession.
Per-Call Services
只有当客户端调用的时候才有instance。
为了说明问题,书中用了很形象的例子。
Code:
public interface IMyContract
{
[OperationContract]
string GetData(int value);
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
// TODO: Add your service operations here
[OperationContract]
void Count();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class MyContract : IMyContract,IDisposable
{
//Other Members
public MyContract()
{
Trace.WriteLine("WcfServiceLibrary1.MyContract()");
}
#region IMyContract Members
int count = 0;
public void Count()
{
count++;
Trace.WriteLine("Counter = " + count);
}
#endregion
#region IDisposable Members
public void Dispose()
{
Trace.WriteLine("WcfServiceLibrary1.Dispose()");
}
#endregion
}
//Tester
ServiceReference1.MyContractClient proxy = new ConsoleApplication1.ServiceReference1.MyContractClient();
proxy.Count();
proxy.Count();
proxy.Count();
proxy.Close();
Console.ReadKey();
结果为:
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
很明显,每次的值都是0+1的结果,这正说明了percall的方式是每个请求一个Instance的。
Per-Session Services
修改上面的例子:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
为:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
结果为:
WcfServiceLibrary1.MyContract()
Counter = 1
Counter = 2
Counter = 3
WcfServiceLibrary1.Dispose()
很明显Instance只有一个了。
我们WcfServiceLibrary默认的Bind是wsHttpBinding,但若是basicHttpBinding,由于每个http到达服务端都是一个新的连接,因此服务端无法判断是哪个连接。
增加服务端app.config中Endpoint。
<endpoint address="basic" binding="basicHttpBinding" name="basic" contract="WcfServiceLibrary1.IMyContract" />
重新导入后修改Program里的程序:
ServiceReference1.MyContractClient proxy = new ConsoleApplication1.ServiceReference1.MyContractClient("basic");
或
ServiceReference1.MyContractClient proxy = new ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");
其中basic和WSHttpBinding_IMyContract为两种不同形式的服务在客户端的Endpoint.Name。
之前默认WSHttpBinding_IMyContract,现在由于存在多个Endpoint,则需要显示指定。
现指定为basic。再次运行,结果:
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
其结果与PerCall是相同的。
通过SessionId可以获得Instance的SessionId
使用Per-Session方式可以通过设置SessionMode属性(允许、必须、不允许三种枚举)。
SessionMode:Gets or sets a value that indicates whether a session is required by the contract.
SessionMode 枚举http://msdn2.microsoft.com/zh-cn/library/system.servicemodel.sessionmode.aspx
|
Allowed(允许)
Specifies that the contract supports sessions if the incoming binding supports them.
如果绑定支持Session的话,则让其支持,否则按照可以支持的方式,比如PerCall的方式进行支持。
|
|
Required(必须)
Specifies that the contract requires a sessionful binding. An exception is thrown if the binding is not configured to support session.
指定契约必须使用Sessionful的方式。如果不支持,则抛出异常。
|
|
NotAllowed(不允许)
Specifies that the contract never supports bindings that initiate sessions.
指定不能使用Sessionful的方式。作者推荐是用NotAllowed的时候仅用PerCall方式。
|
刚才由于我添加了basic的方式,因为默认选中Allowed,因此刚才的之所以结果与PerCall相同,是因为它,下面我将其修改为Required。
将
[ServiceContract]
修改为
[ServiceContract(SessionMode=SessionMode.Required)]
结果为一个运行时错误:
|
System.InvalidOperationException: Contract requires Session, but Binding 'BasicHttpBinding' doesn't support it or isn't configured properly to support it.
at System.ServiceModel.Description.DispatcherBuilder.BuildChannelListener(StuffPerListenUriInfo stuff, ServiceHostBase serviceHost, Uri listenUri, ListenUriMode listenUriMode, Boolean supportContextSession, IChannelListener& result)
at System.ServiceModel.Description.DispatcherBuilder.InitializeServiceHost(ServiceDescription description, ServiceHostBase serviceHost)
at System.ServiceModel.ServiceHostBase.InitializeRuntime()
at System.ServiceModel.ServiceHostBase.OnOpen(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open()
at Microsoft.Tools.SvcHost.ServiceHostHelper.OpenService(ServiceInfo info)
|
但是若使用wsHttpBinding,但却without security and without reliable messaging也将无法维持transport-level session。
运行结果与PerCall的结果相同。其原因也就是因为wsHttpBinding未设置安全可靠的Session。
超时
inactivityTimeout:超时时间
在连接空闲的情况下,以客户端和服务端的超时时间中最短的那个来决定是否移除Instance,若之后再调用则会抛出异常。
作者额外注明可以采用:AutomaticSessionShutdown属性。其设置为true则当proxy.Close()的时候自动关闭Session,设为false的时候则只有在服务端将服务关闭才会关闭Session。
但是,若将其修改为NotAllowed
则结果与PerCall相同(手动写为PerSession)。(不管服务配置如何,它总会是PerCall。因为TCP和IPC协议总是维持transport level,你不能将它们配置SessionMode.NotAllowed,它们会在服务载入时进行验证。作者建议是“在选择使用SessionMode.NotAllowed的同时,将服务配置为PerCall”。)
Singleton Service
Singleton,顾名思义就是仅有一个Instance,供所有客户端调用。
在说明问题之前先修改上面的例子:
//[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
//[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
为:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
|
Tester中:
static void Main(string[] args)
{
ServiceReference1.MyContractClient proxy = new ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");
proxy.Count();
proxy.Count();
proxy.Count();
Console.WriteLine(proxy.Endpoint.Name);
Console.WriteLine(proxy.InnerChannel.SessionId);
proxy.Close();
Console.WriteLine(proxy.Endpoint.Name);
Console.WriteLine(proxy.InnerChannel.SessionId);
Console.WriteLine("_________________________________________________________");
Console.ReadKey();
ServiceReference1.MyContractClient proxy1 = new ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");
proxy1.Count();
proxy1.Count();
proxy1.Count();
Console.WriteLine(proxy1.Endpoint.Name);
Console.WriteLine(proxy1.InnerChannel.SessionId);
proxy1.Close();
Console.WriteLine(proxy1.Endpoint.Name);
Console.WriteLine(proxy1.InnerChannel.SessionId);
Console.WriteLine("_________________________________________________________");
Console.ReadKey();
ServiceReference1.MyContractClient proxy2 = new ConsoleApplication1.ServiceReference1.MyContractClient("basic");
proxy2.Count();
proxy2.Count();
proxy2.Count();
Console.WriteLine(proxy2.Endpoint.Name);
Console.WriteLine(proxy2.InnerChannel.SessionId);
proxy2.Close();
Console.WriteLine(proxy2.Endpoint.Name);
Console.WriteLine(proxy2.InnerChannel.SessionId);
Console.WriteLine("_________________________________________________________");
Console.ReadKey();
}
运行的结果:(Output<Debug>)
//客户端调用前
//...
//其他代码
//...
WcfServiceLibrary1.MyContract()
//...
//其他代码
//...
//客户端调用后
//...
//其他代码
//...
Counter = 1
Counter = 2
Counter = 3
Counter = 4
Counter = 5
Counter = 6
Counter = 7
Counter = 8
Counter = 9
//...
//其他代码
//...
从Counter的值看来,多个proxy调用的是同一个Instance。
值得一提的是WcfServiceLibrary1.MyContract(),也就是构造函数的调用时间是在Service启动的时候,而PerCall与Sessionful构造函数调用时间都是在proxy调用之时。而且只有当Host关闭的时候才会Dispose()。
MSDN:
If the InstanceContextMode value is set to Single the result is that your service can only process one message at a time unless you also set the ConcurrencyMode value to Multiple.
也就是说除非将服务设置成多线程的,否则在一个时间只能处理一个消息请求。
从HOST端控制SingletonInstance
方式一:
修改代码:
无须启动WcfServiceLibrary1(将用外部Host进行启动)直接运行客户端程序:
结果:
//客户端调用前
//...
//其他代码
//...
WcfServiceLibrary1.MyContract()
//...
//其他代码
//...
//客户端调用后
//...
//其他代码
//...
Counter = 201
Counter = 202
Counter = 101
//...
//其他代码
//...
方式二:
通过OperationContext.Current.Host 来获取当前进程的host
此方法我暂时未调出来,大家有想到或做到的麻烦告诉我!
ServiceHost host = OperationContext.Current.Host as ServiceHost;
MyContract singletonInstance = host.SingletonInstance as MyContract;
if (singletonInstance != null)
singletonInstance.CountProperty = DateTime.Now.Second;
先假设以上方法可行吧。
现在我遇到的问题:
1、OperationContext.Current这里的(MSDN:Gets or sets the execution context for the current thread.
)current thread是指我ConsoleApplication也就是Client的线程呢,还是指Service端的线程?(据我常理分析应该是服务端的线程)。理论上我应该在Count()方法内写这段代码,但是问题又涉及到通过Client端进行调用时是使用proxy,这样真正的情况会是怎样呢?
2、因此我又写了一个在一个ConsoleApplication里完成服务的代码,此时在host.SingletonInstance的确是被赋值了(不再是null了),但是紧接着我调用OperationContext.Current却发现其为null,也就是说这里的OperationContext并没有被赋值。继而将其转移到同在一个程序内的MyContract.Count()方法中,但是其值仍然为空,因此此法再度失效。
希望作为高手的您能够提供一个使用OperationContext.Current的场景。什么样算是OperationContext的当前线程?
推荐阅读:
http://www.microsoft.com/china/MSDN/library/Windev/WindowsVista/WCFEssentials.mspx?mfr=true
1、用预编译指令符可以避免在多文件工程中调用文件的时候可能出现的重复定义的现象。
比如:
Main.cpp
#include “Animal.h”
#include “Fish.h”
……
Animal.h
class Animal()
{
}
Fish.h
#include “Animal.h”
class Fish():public Animal
{
}
因此在调用Main.cpp的时候先运行
1→#include “Animal.h” 复制Animal.h过来
class Animal()
{
}
2→#include “Fish.h” 复制Fish.h过来
#include “Animal.h” 复制Animal.h过来
class Animal()
{
}
class Fish():public Animal
{
}
……
因此最后的文件应该是形如:
class Animal()
{
}
class Animal()
{
}
class Fish():public Animal
{
}
因此重复定义了类
class Animal()
{
}
是显而易见的。
这时候引入预编译指令符的方法来避免这样的现象来发生。
假设在最后的文件中我们来补充预编译指令符的方法:
#ifndef ABCD //如果没有定义ABCD,否则转向endif
#define ABCD //那么就定义ABCD
class Animal()
{
}
#endif
#ifndef ABCD //如果没有定义ABCD,否则转向endif
#define ABCD //那么就定义ABCD
class Animal()
{
}
#endif
class Fish():public Animal
{
}
添加蓝色部分就可以避免重复定义了。
因此可以在多文本文件中做如下修改:
Main.cpp
#include “Animal.h”
#include “Fish.h”
……
Animal.h
#ifndef ABCD //如果没有定义ABCD,否则转向endif
#define ABCD //那么就定义ABCD
class Animal()
{
}
#endif
Fish.h
#include “Animal.h”
#ifndef ABCD //如果没有定义ABCD,否则转向endif
#define ABCD //那么就定义ABCD