WCF开发之实例模型(Instancing Modes)
调用场景
• 经典的客户端/服务器(C/S)应用程序
–客户端使用远程,有状态的对象并且在其生命周期内进行控制
• 分布式,可扩展的应用程序
– 通过及时释放远程对象来节约资源的使用
• 分布式单件 (比如:计数器,账单的流水号等)
–多个客户端共享状态
• 经典的无状态Web服务调用
实例模型
• 控制服务实例的生命周期
• InstanceContextMode枚举
– PerCall (每次的服务调用是没有相互关系的,每次都是一次独立的调用)
– PerSession (在同一个Session中的调用时有相互关系的,在不同Session中的调用中是没有相互影响的)
– Single (单件的模型,对所有的调用都是有相互关系的)
• ServiceBehaviorAttribute控制这个设置
PerCall模式
• 为每个调用创建新的服务对象
• PerCall服务增加了整体的吞吐量 (但是不适用于每次调用都耗时较长的操作)
– 状态不会在多次调用中存在
– 服务实例被释放
– 内存开销较小
– 不会产生并发性问题
PerCall体系结构
• 无状态调用
• 为每个请求分别实例化业务逻辑和数据层
• 不存在并发性问题
• 无状态调用可以共享缓存的内容
• 引入并发性问题以及资源占用的问题
利用Cache实际上就是用空间换取时间的一种方法,但是使用时也会带来一些问题,比如并发的处理,或者整体性能的一种平衡,比如:内存只有2G,但是cache要占用4G,这显然就不适合了,所以在开发时要追求一种系统整体的平衡性。
配置PerCall
• InstanceContextMode.PerCall
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]
public class CounterServicePerCall:ICounterServicePerCall
Demo:
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Windows.Forms;
namespace WcfServiceLibrary11
{
[ServiceContract]
public interface ICounterServicePerCall
{
[OperationContract]
int IncrementCounter();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class CounterServicePerCall : ICounterServicePerCall, IDisposable
{
private int m_counter;
public int IncrementCounter()
{
m_counter++;
MessageBox.Show(string.Format("Counter = {0}", m_counter));
return m_counter;
}
public void Dispose()
{
MessageBox.Show("Disposing PerCall object.");
}
}
}
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the config file must be added to the host's
app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service behaviorConfiguration="WcfServiceLibrary11.Service1Behavior"
name="WcfServiceLibrary11.CounterServicePerCall">
<endpoint address="" binding="wsHttpBinding" contract="WcfServiceLibrary11.ICounterServicePerCall">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary11/Service1/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="WcfServiceLibrary11.Service1Behavior">
<!-- To avoid disclosing metadata information,
set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="True"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
MyServiceReference.CounterServicePerCallClient proxy;
public Form1()
{
InitializeComponent();
proxy = new WindowsFormsApplication1.MyServiceReference.CounterServicePerCallClient();
}
private void button1_Click(object sender, EventArgs e)
{
proxy.IncrementCounter();
}
}
}
每次调用的结果都是Counter=1,由此可见每次调用都创建一个新的服务对象。
会话(Session)
• WCF有四种类型的会话:
–传输会话,如:TCP或者命名管道(named pipe)
–可靠性会话
–安全会话
– 应用程序会话
• 应用程序会话是我们这次讨论的主题
• WCF的会话由客户端发起
– ASP.NET的会话由服务器端初始化
PerSession模式 (对于同一个Client而言,Service对象实例是共享的,不同的client的Service对象是分离的)
• 为每个客户端/代理创建新的服务对象
–缺省行为
• 吞吐量较少,内存开销增大
• 状态由服务实例维护
• 引发多线程客户端的并发问题
PerSession体系结构
• 有状态调用
• 每个会话可以缓存下游的业务逻辑和数据层
• 多线程客户端及其之间存在并发性问题
• 状态与会话紧密联系,而不是业务逻辑层
PerSession模式
• 仅当绑定支持会话时,才能够支持会话
• 可以支持会话的绑定:
– NetTcpBinding
– NetNamedPipeBinding
– WSHttpBinding
– WSFederationHttpBinding
– WSDualHttpBinding
配置PerSession
• InstanceContextMode.PerSession
–缺省设置
– 最好能够显式指定
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public classCounterServicePerSession:ICounterServicePerSession
配置会话
• 在服务契约上需要设置能够提供会话功能
• SessionMode 枚举:
– Allowed (缺省)(不推荐使用,原因是配置方式不确定性,容易导致错误)
– NotAllowed
– Required
配置会话
[ServiceContract(Namespace="http://www.thatindigogirl.com/samples/2006/06", SessionMode=SessionMode.Required)]
public interface ICounterServiceSession
{
[OperationContract]
int IncrementCounter();
}
Demo:
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Windows.Forms;
namespace WcfServiceLibrary11
{
[ServiceContract(SessionMode=SessionMode.Required)]
public interface ICounterServicePerSession
{
[OperationContract]
int IncrementCounter();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class CounterServicePerSession : ICounterServicePerSession, IDisposable
{
private int m_counter;
public int IncrementCounter()
{
m_counter++;
MessageBox.Show(string.Format("Counter = {0}", m_counter));
return m_counter;
}
public void Dispose()
{
MessageBox.Show("Disposing PerCall object.");
}
}
}
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the config file must be added to the host's
app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service behaviorConfiguration="WcfServiceLibrary11.Service1Behavior"
name="WcfServiceLibrary11.CounterServicePerSession">
<endpoint address="" binding="wsHttpBinding" contract="WcfServiceLibrary11.ICounterServicePerSession" bindingConfiguration="wsHttpBindingSession">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary11/Service1/" />
</baseAddresses>
</host>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="wsHttpBindingSession" receiveTimeout="00:00:20">
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="WcfServiceLibrary11.Service1Behavior">
<!-- To avoid disclosing metadata information,
set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="True"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
MyServiceReference.CounterServicePerSessionClient proxy;
public Form1()
{
InitializeComponent();
proxy = new WindowsFormsApplication1.MyServiceReference.CounterServicePerSessionClient();
}
private void button1_Click(object sender, EventArgs e)
{
proxy.IncrementCounter();
}
}
}
这个例子可以看到,对于同一个客户端来说counter值是累加的,不同的客户端有不同的counter值。要注意的是,Session的timeout设置,如果客户端在timeout时间的范围内没有任何调用操作,那么在服务端的服务对象会自动销毁,此时如果Client再调用服务,就会发生异常。
会话ID(Session Id)
• 任何形式的会话都会生成会话信道
• 会话标示符用于将消息与正确的信道相关联
– 在会话的整个生命周期中起作用
• SessionId信道的属性
SessionServiceClient proxy=new SessionServiceClient ();
string s = proxy.InnerChannel.SessionId;
会话的生命周期
• 会话的生命周期缺省为持续10分钟
• 在每个绑定上可以通过receiveTimeout设置进行控制
<netTcpBinding>
<binding name="netTcp" receiveTimeout="00:10:00" />
</netTcpBinding>
会话的生命周期
• 可以通过操作显式地控制生命周期
• 设置OperationContractAttribute的属性
IsInitiating:ture表示当调用相关方法时WCF创建Session,false为不创建。
IsTerminating:ture表示当调用完相关方法后WCF销毁Session,false为保留。
[ServiceContract(Namespace ="http://www.thatindigogirl.com/samples/2006/06", SessionMode = SessionMode.Required)]
public interface ISessionService
{
[OperationContract(IsInitiating = true, IsTerminating =false)]
void StartSession();
[OperationContract(IsInitiating = false, IsTerminating =false)]
void IncrementCounter();
[OperationContract(IsInitiating = false, IsTerminating =false)]
int GetCounter();
[OperationContract(IsInitiating = false, IsTerminating =false)]
string GetSessionId();
[OperationContract(IsInitiating = false, IsTerminating =true)]
void StopSession();
}
Demo:
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Windows.Forms;
namespace WcfServiceLibrary11
{
[ServiceContract(SessionMode = SessionMode.Required)]
public interface ISessionService
{
[OperationContract(IsInitiating = true, IsTerminating = false)]
void StartSession();
[OperationContract(IsInitiating = false, IsTerminating = false)]
void IncrementCounter();
[OperationContract(IsInitiating = false, IsTerminating = false)]
int GetCounter();
[OperationContract(IsInitiating = false, IsTerminating = false)]
string GetSessionId();
[OperationContract(IsInitiating = false, IsTerminating = true)]
void StopSession();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class SessionService : ISessionService
{
private int m_counter = 0;
public void StartSession()
{
m_counter = 1;
}
public void IncrementCounter()
{
m_counter++;
}
public int GetCounter()
{
return m_counter;
}
public string GetSessionId()
{
return OperationContext.Current.SessionId;
}
public void StopSession()
{
m_counter = 0;
}
}
}
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the config file must be added to the host's
app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service behaviorConfiguration="WcfServiceLibrary11.Service1Behavior"
name="WcfServiceLibrary11.SessionService">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsHttpBindingSession"
contract="WcfServiceLibrary11.ISessionService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary11/Service1/" />
</baseAddresses>
</host>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="wsHttpBindingSession" receiveTimeout="00:10:20">
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="WcfServiceLibrary11.Service1Behavior">
<!-- To avoid disclosing metadata information,
set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="True"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
MyServiceReference.SessionServiceClient proxy;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
proxy = new WindowsFormsApplication1.MyServiceReference.SessionServiceClient();
}
private void button2_Click(object sender, EventArgs e)
{
proxy.StartSession();
}
private void button3_Click(object sender, EventArgs e)
{
proxy.IncrementCounter();
}
private void button4_Click(object sender, EventArgs e)
{
MessageBox.Show(proxy.GetCounter().ToString());
}
private void button5_Click(object sender, EventArgs e)
{
MessageBox.Show(proxy.GetSessionId());
}
private void button6_Click(object sender, EventArgs e)
{
proxy.StopSession();
}
}
}
单件模式
• 为所有客户端的所有调用创建单一的服务对象
–称之为单件服务
• 通常会给吞吐量带来负面影响
• 潜在的较多内存开销
– 单一的大对象
• 状态由服务的实例维护
• 并发性问题
配置单件
• InstanceContextMode.Single
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class CounterServiceSingle:ICounterServiceSingle
单件的体系结构
• 无状态单件
• 下游业务逻辑和数据层能够被所有的请求共享
–它们通常都是无状态的
• 并发调用会引发并发性问题
• 单件也可以是有状态的
• 特别在处理无状态的业务逻辑对象时
• 状态由服务的会话ID(SessionId)跟踪
– 只有一个单件的对象
Demo:
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Windows.Forms;
namespace WcfServiceLibrary11
{
[ServiceContract(SessionMode = SessionMode.Required)]
public interface ICounterServiceSingle
{
[OperationContract]
int IncrementCounter();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode=ConcurrencyMode.Single)]
public class CounterServiceSingle : ICounterServiceSingle
{
private int m_counter = 0;
public int IncrementCounter()
{
m_counter++;
MessageBox.Show(string.Format("Counter={0}, SessionID={1}", m_counter, OperationContext.Current.SessionId));
return m_counter;
}
}
}
这样的话,每个客户端都共享了同一个服务对象,也就共享了同一个Counter。
会话总结
• PerCall适用于
–用于高可扩展性和高吞吐量的业务
• 使用PerSession服务时需要注意
–注意会话所带来的开销和潜在的超时问题
• 通常要避免使用单件模型
– 当多台客户端主机共享某个功能的时候非常有用