调用场景
• 经典的客户端/服务器(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:
ICounterServicePerCall 代码
using
System;
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.
"
);
}
}
}
Config 代码
<?
xml version
=
"
1.0
"
encoding
=
"
utf-8
"
?>
<
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
>
Client 代码
using
System;
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:
Service代码
using
System;
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.
"
);
}
}
}
Config 代码
<?
xml version="1.0" encoding="utf-8"
?>
<
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
>
Client代码
using
System;
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:
Service 代码
using
System;
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
;
}
}
}
Config 代码
<?
xml version="1.0" encoding="utf-8"
?>
<
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
>
Client 代码
using
System;
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:
Service 代码
using
System;
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服务时需要注意
–注意会话所带来的开销和潜在的超时问题
• 通常要避免使用单件模型
– 当多台客户端主机共享某个功能的时候非常有用