复习WCF(8)----- 会话,实例化和并发
会话
“会话”是在两个终结点(endpoint)之间发送的所有消息的一种相互关系,它和ASP.NET的会话还是很不一样的
如何在服务协定上设置会话呢? 设置ServiceContract的SessionMode

Allowed(默认):客户端可以进行连接,并选择建立会话或不建立会话
Required:所有调用都必须是同一个对话的一部分(客户端必须在一个会话里面调用所有的操作)
NotAllowed :禁止会话
ASP.NET会话和WCF会话的区别
ASP.NET会话总是由服务器端启动
ASP.NET会话原本是无序的
ASP.NET会话提供了种跨请求的常规数据存储机制(比如一个Session就存储了一个会话的所有相关值)
当WCF服务接受客户端会话时,默认情况下启用以下功能
通过同一用户定义的服务对象来处理WCF客户端对象之间的所有调用(意思就是说,我们服务对象是同一个对象来处理所有操作的调用)
除了此实例化行为之外,不同的基于会话的绑定还会提供其他功能(不同的绑定有不同的会话类型,这是绑定的内容)
系统提供的会话类型
System.ServiceModel.Channels.SecurityBindingElement 支持基于安全的会话,其中,两个通信端采用统一的安全对话
System.ServiceModel.WSHttpBinding绑定(包含对安全会话和可靠会话的支持)默认情况下只使用对消息进行加密和数组签名的安全会话
System.ServiceModel.NetTcpBinding绑定支持基于TCP/IP的会话,以确保所有消息都由套接字级别的连接进行关联
System.ServiceModel.Channels.RelibleSessionBindingElement元素实现WS-ReliableMessaging规范,并提供对可靠会话的支持。在可靠会话中,可以配置消息以按顺序传递并且只传递一次,从而使消息在对话期间即使经过多个节点也可以确保保密性。
System.ServiceModel.NetMsmqBinding绑定提供MSMQ数据报会话
Demo
项目工程图:

服务端IService1.cs:
using System.ServiceModel;
namespace WcfServiceLibrary1
{
[ServiceContract(SessionMode = SessionMode.Required)] //必须在会话内调用所有的服务
public interface ICalculatorSession
{
[OperationContract(IsOneWay = true)]
void Clear();
[OperationContract(IsOneWay = true)]
void AddTo(double n);
[OperationContract(IsOneWay = true)]
void SubtractFrom(double n);
[OperationContract(IsOneWay = true)]
void MultiplyBy(double n);
[OperationContract(IsOneWay = true)]
void DivideBy(double n);
[OperationContract]
double Result();
}
}
Service1.cs:
using System.ServiceModel;
namespace WcfServiceLibrary1
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] // 所有针对这些服务的调用,在服务器上都是用单个实例来操作的
public class CalculatorService : ICalculatorSession
{
double result = 0.0D;
public void Clear()
{
result = 0.0D;
}
public void AddTo(double n)
{
result += n;
}
public void SubtractFrom(double n)
{
result -= n;
}
public void MultiplyBy(double n)
{
result *= n;
}
public void DivideBy(double n)
{
result /= n;
}
public double Result()
{
return result;
}
}
}
App.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<system.serviceModel>
<services>
<service name="WcfServiceLibrary1.CalculatorService" behaviorConfiguration="WcfServiceLibrary1.CalculatorService">
<host>
<baseAddresses>
<add baseAddress = "http://localhost:8732/Design_Time_Addresses/WcfServiceLibrary1/CalculatorService/" />
</baseAddresses>
</host>
<endpoint address ="" binding="wsHttpBinding" contract="WcfServiceLibrary1.ICalculatorSession">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="WcfServiceLibrary1.CalculatorService">
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
客户端Program.cs:
using System;
using client.ServiceReference1;
namespace client
{
class Program
{
static void Main(string[] args)
{
CalculatorSessionClient client = new CalculatorSessionClient();
//*********************这一些列都是一个会话内的操作***************************
client.Clear();
//因为服务协定是单向的(加减乘除都是单向的没有返回值的) ,调用了加100以后,并没有把它的值拿到,当然它肯定不是存在服务器上
//因为服务器是没有状态的,它会保存在会话中,反正只要知道并不是存在服务器,是由WCF基础结构来承载这些数据就好了
client.AddTo(100);
client.SubtractFrom(50);
client.MultiplyBy(17.65);
client.DivideBy(2.0);
double result = client.Result();
//*********************这一些列都是一个会话内的操作***************************
Console.WriteLine("(((0 + 100) - 50) * 17.65) / 2 = {0}", result);
client.Close();
Console.WriteLine();
Console.WriteLine("Press <ENTER> to terminate client.");
Console.ReadLine();
}
}
}
最终,一系列操作后得到的值能够获取到, 结果如下

这个例子的重点在Program.cs的注释里。
然后其实并没有看到打开会话的操作。其实在第一个操作里client.Clear(); 就已经启用了会话
实例化
实例化行为(使用System.ServiceModel.ServiceBehaviorAttribute.InstanceContextMode属性进行设置)控制如何创建InstanceContext以响应传入的消息。
因为最终在接收到客户端请求后执行服务操作,它需要根据客户端传入的方式来实例化相应的对象,来处理客户端的请求,然后再把处理结果返回给客户端。
(可以看到上面的服务实现类有个InstanceContextMode = InstanceContextMode.Single的设置)
默认情况下,每个InstanceContext都与一个用户定义服务对象相关联,因此(在默认情况下)设置InstanceContextModel属性也可以控制用户定义服务对象的实例化
InstanceContextMode
PerCall:为每个客户端请求创建一个新的InstanceContext(以及相应的服务对象)
PerSession:为每个新的客户端会话创建一个新的InstanceContext(以及相应的服务对象),并在该会话的生存期内对其维护(这需要使用支持会话的绑定)
Single:单个InstanceContext(以及相应的服务对象)处理应用程序生存期内的所有客户端请求(所有的请求都由一个对象处理,所以可能会堵塞什么的)
Demo
项目工程图:

服务端IService1.cs:
using System.ServiceModel;
namespace WcfServiceLibrary1
{
[ServiceContract(SessionMode = SessionMode.Required)] //也需要会话支持
public interface ICalculator //提供加减乘除服务
{
[OperationContract]
double Add(double n1, double n2);
[OperationContract]
double Subtract(double n1, double n2);
[OperationContract]
double Multiply(double n1, double n2);
[OperationContract]
double Divide(double n1, double n2);
}
[ServiceContract(SessionMode = SessionMode.Required)] //也需要会话支持
public interface ICalculatorInstance : ICalculator //派生的协定
{
[OperationContract]
string GetInstanceContextMode(); //获得当前的实例化模式
[OperationContract]
int GetInstanceId(); //获取当前的实例ID
[OperationContract]
int GetOperationCount(); //获取操作的次数
}
}
Service1.cs:
using System;
using System.ServiceModel;
using System.Threading;
namespace WcfServiceLibrary1
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] //一个会话一个对象
//[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
//[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class CalculatorService : ICalculatorInstance
{
static Object syncObject = new object();
static int instanceCount;
int instanceId;
int operationCount;
public CalculatorService()
{
lock (syncObject)
{
instanceCount++; //只要CalculatorService服务对象实例化 实例化次数就+1
instanceId = instanceCount;
}
}
public double Add(double n1, double n2)
{
operationCount++; //每调用一次Add服务 操作次数就+1
return n1 + n2;
}
public double Subtract(double n1, double n2)
{
Interlocked.Increment(ref operationCount);
return n1 - n2;
}
public double Multiply(double n1, double n2)
{
Interlocked.Increment(ref operationCount);
return n1 * n2;
}
public double Divide(double n1, double n2)
{
Interlocked.Increment(ref operationCount);
return n1 / n2;
}
/// <summary>
/// 获取实例化模式
/// </summary>
/// <returns></returns>
public string GetInstanceContextMode()
{
ServiceHost host = (ServiceHost)OperationContext.Current.Host;
ServiceBehaviorAttribute behavior = host.Description.Behaviors.Find<ServiceBehaviorAttribute>();
return behavior.InstanceContextMode.ToString();
}
public int GetInstanceId()
{
return instanceId;
}
public int GetOperationCount()
{
lock (syncObject)
{
return operationCount;
}
}
}
}
App.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<system.serviceModel>
<services>
<service name="WcfServiceLibrary1.CalculatorService" behaviorConfiguration="WcfServiceLibrary1.CalculatorService">
<host>
<baseAddresses>
<add baseAddress = "http://localhost:8732/Design_Time_Addresses/WcfServiceLibrary1/CalculatorService/" />
</baseAddresses>
</host>
<endpoint address ="" binding="wsHttpBinding" contract="WcfServiceLibrary1.ICalculatorInstance" bindingConfiguration="BindingWithSession">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="BindingWithSession">
<reliableSession enabled="true"/>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="WcfServiceLibrary1.CalculatorService">
<!-- 为避免泄漏元数据信息,
请在部署前将以下值设置为 false 并删除上面的元数据终结点 -->
<serviceMetadata httpGetEnabled="True"/>
<!-- 要接收故障异常详细信息以进行调试,
请将以下值设置为 true。在部署前设置为 false
以避免泄漏异常信息 -->
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
客户端Program.cs:
using System;
using client.ServiceReference1;
namespace client
{
class Program
{
static void Main(string[] args)
{
//实例化一个代理类对象
CalculatorInstanceClient client = new CalculatorInstanceClient(); //首先创建客户端代理类实例
string instanceMode = client.GetInstanceContextMode(); //得到实例化模式
Console.WriteLine("InstanceContextMode: {0}", instanceMode); //输出模式
DoCalculations(client);
//再执行完上面的DoCalculations函数后,紧接着又实例化一个新的代理类对象
CalculatorInstanceClient client2 = new CalculatorInstanceClient();
//然后又重新调用了一遍
DoCalculations(client2);
client.Close();
Console.WriteLine();
Console.WriteLine("Press <ENTER> to terminate client.");
Console.ReadLine();
}
//利用代理类对象 调用加减乘除一系列方法/服务
static void DoCalculations(CalculatorInstanceClient client)
{
// Call the Add service operation.
double value1 = 100.00D;
double value2 = 15.99D;
double result = client.Add(value1, value2);
Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);
//调用完后输出实例化次数和操作次数
Console.Write("InstanceId: {0}", client.GetInstanceId());
Console.WriteLine(" , OperationCount: {0}", client.GetOperationCount());
value1 = 145.00D;
value2 = 76.54D;
result = client.Subtract(value1, value2);
Console.WriteLine("Subtract({0},{1}) = {2}", value1, value2, result);
Console.Write("InstanceId: {0}", client.GetInstanceId());
Console.WriteLine(" , OperationCount: {0}", client.GetOperationCount());
value1 = 9.00D;
value2 = 81.25D;
result = client.Multiply(value1, value2);
Console.WriteLine("Multiply({0},{1}) = {2}", value1, value2, result);
Console.Write("InstanceId: {0}", client.GetInstanceId());
Console.WriteLine(" , OperationCount: {0}", client.GetOperationCount());
value1 = 22.00D;
value2 = 7.00D;
result = client.Divide(value1, value2);
Console.WriteLine("Divide({0},{1}) = {2}", value1, value2, result);
Console.Write("InstanceId: {0}", client.GetInstanceId());
Console.WriteLine(" , OperationCount: {0}", client.GetOperationCount());
}
}
}
这个demo的重点在于服务端Service1.cs的实例化模式,稍后将分别一个个测试结果,如图

PerSession的结果如下图:

可以看出每个代理类对象都会重新实例化一次,同一个对象内的操作都会递加。所以得出PerSession模式是一个会话一个实例
PerCall的结果如下图:

可以看出实例次数一直在增加(3,6,9,12,15,18,21,24),而操作次数一直都是0。
所以可以得出,PerCall模式是每次调用都创建一个新实例
Single的结果如下图:

这就更加明显了吧,都是同一个实例
并发
并发是对InstanceContext中在任一时刻处于活动状态的线程数量控制
此控制是通过将System.ServiceModel.ServiceBehaviorAttribute.ConcurrencyMode与ConcurrencyMode枚举结合使用来实现的
ConcurrencyMode
Single:最多允许每个实例上下文同时拥有一个对该实例上下文的消息进行处理的线程。其他希望使用同一个实例上下文的线程必须一直阻塞,知道原始线程退出该实例上下文为止,其实也就是单线程
Multiple:每个服务实例都可以拥有多个同时处理消息的线程,若要使用此并发模式,服务实现必须是线程安全的
Reentrant:每个服务实例一次只能处理一个消息,但可以接受可重入的操作调用。仅当服务通过Wcf客户端对象提供服务时才会接受这些调用。(可能有点难理解,待会以例子来看吧)
并发的使用与实例化模式有关。在PerCall实例化过程中,与并发没有关系,因为每个消息都由一个新的InstanceContext处理,因此InstancContext中处于活动的线程永远不会超过1个
会话与InstanceContext设置进行交互
SessionMode和InstanceContextMode组合的情况下,支持会话或不支持会话的传入通道的结果 如图:

demo实例化与并发
项目工程图:

服务项目我是用的一个普通的类库,新建了svc和web.config文件
Web.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service
name="service.CalculatorService"
behaviorConfiguration="CalculatorServiceBehavior">
<endpoint address=""
binding="wsHttpBinding"
contract="service.ICalculatorConcurrency" />
<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
<!--For debugging purposes set the includeExceptionDetailInFaults attribute to true-->
<behaviors>
<serviceBehaviors>
<behavior name="CalculatorServiceBehavior">
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
service.svc:
<%@ServiceHost language=c# Debug="true" Service="service.CalculatorService" %>
using System;
using System.ServiceModel;
namespace service
{
[ServiceContract]
public interface ICalculator
{
[OperationContract]
double Add(double n1, double n2);
[OperationContract]
double Subtract(double n1, double n2);
[OperationContract]
double Multiply(double n1, double n2);
[OperationContract]
double Divide(double n1, double n2);
}
[ServiceContract]
public interface ICalculatorConcurrency : ICalculator
{
[OperationContract]
string GetConcurrencyMode();
[OperationContract]
int GetOperationCount();
}
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single, InstanceContextMode = InstanceContextMode.Single)]
public class CalculatorService : ICalculatorConcurrency
{
int operationCount;
public double Add(double n1, double n2)
{
operationCount++;
System.Threading.Thread.Sleep(180);
return n1 + n2;
}
public double Subtract(double n1, double n2)
{
operationCount++;
System.Threading.Thread.Sleep(100);
return n1 - n2;
}
public double Multiply(double n1, double n2)
{
operationCount++;
System.Threading.Thread.Sleep(150);
return n1 * n2;
}
public double Divide(double n1, double n2)
{
operationCount++;
System.Threading.Thread.Sleep(120);
return n1 / n2;
}
public string GetConcurrencyMode()
{
ServiceHost host = (ServiceHost)OperationContext.Current.Host;
ServiceBehaviorAttribute behavior = host.Description.Behaviors.Find<ServiceBehaviorAttribute>();
return behavior.ConcurrencyMode.ToString();
}
public int GetOperationCount()
{
return operationCount;
}
}
}
然后生成服务项目,将svc和config部署到IIS上,用svcutil生成客户端代理类。
生成的时候注意加上/async ,这是生成一份同步调用和一份异步调用的参数 如图

客户端Program.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace client
{
class Program
{
static void Main(string[] args)
{
CalculatorConcurrencyClient client = new CalculatorConcurrencyClient();
Console.WriteLine("Press <ENTER> to terminate client once the output is displayed.");
Console.WriteLine();
client.BeginGetConcurrencyMode(GetConcurrencyModeCallback, client);
double value1 = 100.00D;
double value2 = 15.99D;
IAsyncResult arAdd = client.BeginAdd(value1, value2, AddCallback, client);
Console.WriteLine("Add({0},{1})", value1, value2);
value1 = 145.00D;
value2 = 76.54D;
IAsyncResult arSubtract = client.BeginSubtract(value1, value2, SubtractCallback, client);
Console.WriteLine("Subtract({0},{1})", value1, value2);
value1 = 9.00D;
value2 = 81.25D;
IAsyncResult arMultiply = client.BeginMultiply(value1, value2, MultiplyCallback, client);
Console.WriteLine("Multiply({0},{1})", value1, value2);
value1 = 22.00D;
value2 = 7.00D;
IAsyncResult arDivide = client.BeginDivide(value1, value2, DivideCallback, client);
Console.WriteLine("Divide({0},{1})", value1, value2);
client.BeginGetOperationCount(GetOperationCountCallback, client);
Console.ReadLine();
client.Close();
}
static void GetConcurrencyModeCallback(IAsyncResult ar)
{
string result = ((CalculatorConcurrencyClient)ar.AsyncState).EndGetConcurrencyMode(ar);
Console.WriteLine("ConcurrencyMode : {0}", result);
}
static void GetOperationCountCallback(IAsyncResult ar)
{
int result = ((CalculatorConcurrencyClient)ar.AsyncState).EndGetOperationCount(ar);
Console.WriteLine("OperationCount : {0}", result);
}
static void AddCallback(IAsyncResult ar)
{
double result = ((CalculatorConcurrencyClient)ar.AsyncState).EndAdd(ar);
Console.WriteLine("Add Result : " + result);
}
static void SubtractCallback(IAsyncResult ar)
{
double result = ((CalculatorConcurrencyClient)ar.AsyncState).EndSubtract(ar);
Console.WriteLine("Subtract Result : " + result);
}
static void MultiplyCallback(IAsyncResult ar)
{
double result = ((CalculatorConcurrencyClient)ar.AsyncState).EndMultiply(ar);
Console.WriteLine("Multiply Result : " + result);
}
static void DivideCallback(IAsyncResult ar)
{
double result = ((CalculatorConcurrencyClient)ar.AsyncState).EndDivide(ar);
Console.WriteLine("Divide Result : " + result);
}
}
}
ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single测试:

虽然InstanceContextMode = InstanceContextMode.Single(只有一个实例),但OperationCount却为1
ConcurrencyMode = ConcurrencyMode.Single, InstanceContextMode = InstanceContextMode.Single测试

关于这个问题的解决办法 http://social.microsoft.com/Forums/zh-CN/wcfzhchs/thread/c990c287-c422-4550-9501-c04395d9ea15
只要设置InstanceContextMode =InstanceContextMode.PerCall ,ConcurrencyMode =ConcurrencyMode.Multiple就好了

浙公网安备 33010602011771号