一、引言

  由前面几篇博文我们知道,WCF是微软基于SOA建立的一套在分布式环境中各个相对独立的应用进行交流(Communication)的框架,它实现了最新的基于WS-*规范。按照SOA的原则,相对独自的业务逻辑以Service的形式进行封装,调用者通过消息(Messaging)的方式来调用服务。对于承载某个业务功能实现的服务应该具有上下文(Context)无关性,意思就是说构造服务的操作(Operation)不应该绑定到具体的调用上下文,对于任何的调用,具有什么的样输入就会对应怎样的输出。因为SOA一个最大的目标是尽可能地实现重用,只有具有Context无关性,服务才能最大限度的重用。即从软件架构角度理解为,一个模块只有尽可能的独立,即具有上下文无关性,才能被最大限度的重用。软件体系一直在强调低耦合也是这个道理。

  但是在某些场景下,我们却希望系统为我们创建一个Session来保留Client和Service的交互的状态,如Asp.net中Session的机制一样,WCF也提供了对Session的支持。下面就具体看看WCF中对Session的实现。

二、WCF中Session详细介绍

2.1 Asp.net的Session与WCF中的Session

  在WCF中,Session属于Service Contract的范畴,并在Service Contract定义中通过SessionModel参数来实现。WCF中会话具有以下几个重要的特征:

  • Session都是由Client端显示启动和终止的。

  在WCF中Client通过创建的代理对象来和服务进行交互,在支持Session的默认情况下,Session是和具体的代理对象绑定在一起,当Client通过调用代理对象的某个方法来访问服务时,Session就被初始化,直到代理的关闭,Session则被终止。我们可以通过两种方式来关闭Proxy:一是调用ICommunicationObject.Close 方法,二是调用ClientBase<TChannel>.Close 方法 。我们也可以通过服务中的某个操作方法来初始化、或者终止Session,可以通过OperationContractAttribute的IsInitiating和IsTerminating参数来指定初始化和终止Session的Operation。

  • 在WCF会话期间,传递的消息按照它发送的顺序被接收。
  • WCF并没有为Session的支持保存相关的状态数据。

  讲到Session,做过Asp.net开发的人,自然想到的就是Asp.net中的Session。它们只是名字一样,在实现机制上有很大的不同。Asp.net中的Session具有以下特性:

  • Asp.net的Session总是由服务端启动的,即在服务端进行初始化的。
  • Asp.net中的Session是无需,不能保证请求处理是有序的。
  • Asp.net是通过在服务端以某种方式保存State数据来实现对Session的支持,例如保存在Web Server端的内存中。

2.2 WCF中服务实例管理

  对于Client来说,它实际上不能和Service进行直接交互,它只能通过客户端创建的Proxy来间接地和Service进行交互,然而真正的调用而是通过服务实例来进行的。我们把通过Client的调用来创建最终的服务实例过程称作激活,在.NET Remoting中包括Singleton模式、SingleCall模式和客户端激活方式,WCF中也有类似的服务激活方式:单调服务(PerCall)、会话服务(PerSession)和单例服务(Singleton)。

  • 单调服务(Percall):为每个客户端请求分配一个新的服务实例。类似.NET Remoting中的SingleCall模式
  • 会话服务(Persession):在会话期间,为每次客户端请求共享一个服务实例,类似.NET Remoting中的客户端激活模式。
  • 单例服务(Singleton):所有客户端请求都共享一个相同的服务实例,类似于.NET Remoting的Singleton模式。但它的激活方式需要注意一点:当为对于的服务类型进行Host的时候,与之对应的服务实例就被创建出来,之后所有的服务调用都由这个服务实例进行处理。

  WCF中服务激活的默认方式是PerSession,但不是所有的Bingding都支持Session,比如BasicHttpBinding就不支持Session。你也可以通过下面的方式使ServiceContract不支持Session.

[ServiceContract(SessionMode = SessionMode.NotAllowed)]

  下面分别介绍下这三种激活方式的实现。

三、WCF中实例管理的实现

  WCF中服务激活的默认是PerSession的方式,下面就看看PerSession的实现方式。我们还是按照前面几篇博文的方式来实现使用PerSession方式的WCF服务程序。

  第一步:自然是实现我们的WCF契约和契约的服务实现。具体的实现代码如下所示:

 // 服务契约的定义
[ServiceContract]
public interface ICalculator
{
[OperationContract(IsOneWay = true)]
void Increase(); [OperationContract]
int GetResult();
} // 契约的实现
public class CalculatorService : ICalculator, IDisposable
{
private int _nCount = ; public CalculatorService()
{
Console.WriteLine("CalulatorService object has been created");
} // 为了看出服务实例的释放情况
public void Dispose()
{
Console.WriteLine("CalulatorService object has been Disposed");
} #region ICalulator Members
public void Increase()
{
// 输出Session ID
Console.WriteLine("The Add method is invoked and the current session ID is: {0}", OperationContext.Current.SessionId);
this._nCount++;
} public int GetResult()
{
Console.WriteLine("The GetResult method is invoked and the current session ID is: {0}", OperationContext.Current.SessionId);
return this._nCount;
}
#endregion
}

  为了让大家对服务对象的创建和释放有一个直观的认识,我特意对服务类实现了构造函数和IDisposable接口,同时在每个操作中输出当前的Session ID。

  第二步:实现服务宿主程序。这里还是采用控制台程序作为服务宿主程序,具体的实现代码如下所示:

 // 服务宿主程序
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
{
host.Opened += delegate
{
Console.WriteLine("The Calculator Service has been started, begun to listen request...");
}; host.Open();
Console.ReadLine();
}
}
}

  对应的配置文件为:

<!--服务宿主的配置文件-->
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name ="CalculatorBehavior">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="WCFContractAndService.CalculatorService" behaviorConfiguration="CalculatorBehavior">
<endpoint address="" binding="basicHttpBinding" contract="WCFContractAndService.ICalculator"/>
<host>
<baseAddresses>
<add baseAddress="http://localhost:9003/CalculatorPerSession"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>

  第三步:实现完了服务宿主程序,接下来自然是实现客户端程序来访问服务操作。这里的客户端也是控制台程序,具体的实现代码如下所示:

 // 客户端程序实现
class Program
{
static void Main(string[] args)
{
// Use ChannelFactory<ICalculator> to create WCF Service proxy
ChannelFactory<ICalculator> calculatorChannelFactory = new ChannelFactory<ICalculator>("HttpEndPoint");
Console.WriteLine("Create a calculator proxy :proxy1");
ICalculator proxy1 = calculatorChannelFactory.CreateChannel();
Console.WriteLine("Invoke proxy1.Increate() method");
proxy1.Increase();
Console.WriteLine("Invoke proxy1.Increate() method again");
proxy1.Increase();
Console.WriteLine("The result return via proxy1.GetResult() is: {0}", proxy1.GetResult()); Console.WriteLine("Create another calculator proxy: proxy2");
ICalculator proxy2 = calculatorChannelFactory.CreateChannel();
Console.WriteLine("Invoke proxy2.Increate() method");
proxy2.Increase();
Console.WriteLine("Invoke proxy2.Increate() method again");
proxy2.Increase();
Console.WriteLine("The result return via proxy2.GetResult() is: {0}", proxy2.GetResult()); Console.ReadLine();
}
}

  客户端对应的配置文件内容如下所示:

<!--客户端配置文件-->
<configuration>
<system.serviceModel>
<client>
<endpoint address="http://localhost:9003/CalculatorPerSession"
binding="basicHttpBinding"
contract="WCFContractAndService.ICalculator" name="HttpEndPoint"/>
</client>
</system.serviceModel>
</configuration>

  经过上面三步,我们就完成了PerSession方式的WCF程序了,下面看看该程序的运行结果。

  首先,以管理员权限运行服务寄宿程序,运行成功后,你将看到如下图所示的画面:

  接下来,运行客户端对服务操作进行调用,运行成功后,你将看到服务宿主的输出和客户端的输出情况如下图所示:

  从客户端的运行结果可以看出,虽然我们两次调用了Increase方法来增加_nCount的值,但是最终的运行结果仍然是0。这样的运行结果好像与我们之前所说的WCF默认Session支持矛盾,因为如果WCF默认的方式PerSession的话,则服务实例是和Proxy绑定在一起,当Proxy调用任何一个操作的时候Session开始,从此Session将会与Proxy具有一样的生命周期。按照这个描述,客户端运行的结果应该是2而不是0。这里,我只能说运行结果并没有错,因为有图有真相嘛,那到底是什么原因导致客户端获得_nCount值是0呢?其实在前面已经讲到过,并不是所有的绑定都是支持Session的,上面程序的实现我们使用的basicHttpBinding,而basicHttpBinding是不支持Session方式的,所以WCF会采用PerCall的方式创建Service Instance,所以在服务端中对于每一个Proxy都有3个对象被创建,两个是对Increase方法的调用会导致服务实例的激活,另一个是对GetResult方法的调用导致服务实例的激活。因为是PerCall方式,所以每次调用完之后,就会对服务实例进行释放,所以对应的就有3行服务对象释放输出。并且由于使用的是不支持Session的binding,所以Session ID的输出也为null。所以,上面WCF程序其实是PerCall方式的实现。

  既然,上面的运行结果是由于使用了不支持Session的basicHttpBinding导致的,下面看看使用一个支持Session的Binding:wsHttpBinding来看看运行结果是怎样的,这里的修改很简单,只需要把宿主和客户端的配置文件把绑定类型修改为wsHttpBinding就可以了。

<!--客户端配置文件-->
<configuration>
<system.serviceModel>
<client>
<endpoint address="http://localhost:9003/CalculatorPerSession"
binding="wsHttpBinding"
contract="WCFContractAndService.ICalculator" name="HttpEndPoint"/>
</client>
</system.serviceModel>
</configuration>
<!--服务宿主的配置文件-->
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name ="CalculatorBehavior">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="WCFContractAndService.CalculatorService" behaviorConfiguration="CalculatorBehavior">
<endpoint address="" binding="wsHttpBinding" contract="WCFContractAndService.ICalculator"/>
<host>
<baseAddresses>
<add baseAddress="http://localhost:9003/CalculatorPerSession"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>

  现在我们再运行下上面的程序来看看此时的执行结果,具体的运行结果如下图所示:

  从上面的运行结果可以看出,此时两个Proxy的运行结果都是2,可以看出此时服务激活方式采用的是PerSession方式。此时对于服务端就只有两个服务实例被创建了,并且对于每个服务实例具有相同的Session ID。 另外由于Client的Proxy还依然存在,服务实例也不会被回收掉,从上面服务端运行的结果也可以证实这点,因为运行结果中没有对象呗Disposable的输出。你可以在客户端显式调用ICommunicationObject.Close方法来显式关闭掉Proxy,在客户端添加对Proxy的显示关闭代码,此时客户端的代码修改为如下所示:

 // 客户端程序实现
class Program
{
static void Main(string[] args)
{
// Use ChannelFactory<ICalculator> to create WCF Service proxy
ChannelFactory<ICalculator> calculatorChannelFactory = new ChannelFactory<ICalculator>("HttpEndPoint");
Console.WriteLine("Create a calculator proxy :proxy1");
ICalculator proxy1 = calculatorChannelFactory.CreateChannel();
Console.WriteLine("Invoke proxy1.Increate() method");
proxy1.Increase();
Console.WriteLine("Invoke proxy1.Increate() method again");
proxy1.Increase();
Console.WriteLine("The result return via proxy1.GetResult() is: {0}", proxy1.GetResult());
(proxy1 as ICommunicationObject).Close(); // 显示关闭Proxy Console.WriteLine("Create another calculator proxy: proxy2");
ICalculator proxy2 = calculatorChannelFactory.CreateChannel();
Console.WriteLine("Invoke proxy2.Increate() method");
proxy2.Increase();
Console.WriteLine("Invoke proxy2.Increate() method again");
proxy2.Increase();
Console.WriteLine("The result return via proxy2.GetResult() is: {0}", proxy2.GetResult());
(proxy2 as ICommunicationObject).Close();

Console.ReadLine();
}
}

  此时,服务对象的Dispose()方法将会调用,此时服务端的运行结果如下图所示:

  上面演示了默认支持Session的情况,下面我们修改服务契约使之不支持Session,此时只需要知道ServiceContract的SessionMode为NotAllowed即可。

[ServiceContract(SessionMode= SessionMode.NotAllowed)] // 是服务契约不支持Session
public interface ICalculator
{
[OperationContract(IsOneWay = true)]
void Increase(); [OperationContract]
int GetResult();
}

  此时,由于服务契约不支持Session,此时服务激活方式采用的仍然是PerCall。运行结果与前面采用不支持Session的绑定的运行结果一样,这里就不一一贴图了。

  除了通过显式修改ServiceContract的SessionMode来使服务契约支持或不支持Session外,还可以定制操作对Session的支持。定制操作对Session的支持可以通过OperationContract的IsInitiating和InTerminating属性设置。

  // 服务契约的定义
[ServiceContract(SessionMode= SessionMode.Required)] // 显式使服务契约支持Session
public interface ICalculator
{
// IsInitiating:该值指示方法是否实现可在服务器上启动会话(如果存在会话)的操作,默认值是true
// IsTerminating:获取或设置一个值,该值指示服务操作在发送答复消息(如果存在)后,是否会导致服务器关闭会话,默认值是false
[OperationContract(IsOneWay = true, IsInitiating =true, IsTerminating=false )]
void Increase(); [OperationContract(IsInitiating = true, IsTerminating = true)]
int GetResult();
}

  在上面代码中,对两个操作都设置InInitiating的属性为true,意味着调用这两个操作都会启动会话,而把GetResult操作的IsTerminating设置为true,意味着调用完这个操作后,会导致服务关闭掉会话,因为在Session方式下,Proxy与Session有一致的生命周期,所以关闭Session也就是关闭proxy对象,所以如果后面再对proxy对象的任何一个方法进行调用将会导致异常,下面代码即演示了这种情况。

 // 客户端程序实现
class Program
{
static void Main(string[] args)
{
// Use ChannelFactory<ICalculator> to create WCF Service proxy
ChannelFactory<ICalculator> calculatorChannelFactory = new ChannelFactory<ICalculator>("HttpEndPoint");
Console.WriteLine("Create a calculator proxy :proxy1");
ICalculator proxy1 = calculatorChannelFactory.CreateChannel();
Console.WriteLine("Invoke proxy1.Increate() method");
proxy1.Increase();
Console.WriteLine("Invoke proxy1.Increate() method again");
proxy1.Increase();
Console.WriteLine("The result return via proxy1.GetResult() is: {0}", proxy1.GetResult());
try
16 {
17 proxy1.Increase(); // session关闭后对proxy1.Increase方法调用将会导致异常
18 }
19 catch (Exception ex) // 异常捕获
20 {
21 Console.WriteLine("在Session关闭后调用Increase方法失败,错误信息为:{0}", ex.Message);
22 }

Console.WriteLine("Create another calculator proxy: proxy2");
ICalculator proxy2 = calculatorChannelFactory.CreateChannel();
Console.WriteLine("Invoke proxy2.Increate() method");
proxy2.Increase();
Console.WriteLine("Invoke proxy2.Increate() method again");
proxy2.Increase();
Console.WriteLine("The result return via proxy2.GetResult() is: {0}", proxy2.GetResult()); Console.ReadLine();
}
}

  此时运行结果也验证我们上面的分析,客户端和服务端的运行结果如下图所示:

  上面演示了PerSession和PerCall的两种服务对象激活方式,下面看看Single的激活方式运行的结果。首先通过ServiceBehavior的InstanceContextMode属性显式指定激活方式为Single,由于ServiceBehaviorAttribute特性只能应用于类上,所以把该特性应用于CalculatorService类上,此时服务实现的代码如下所示:

// 契约的实现
// ServiceBehavior属性只能应用在类上
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] // 显示指定PerSingle方式
public class CalculatorService : ICalculator, IDisposable
{
private int _nCount = ; public CalculatorService()
{
Console.WriteLine("CalulatorService object has been created");
} // 为了看出服务实例的释放情况
public void Dispose()
{
Console.WriteLine("CalulatorService object has been Disposed");
} #region ICalulator Members
public void Increase()
{
// 输出Session ID
Console.WriteLine("The Add method is invoked and the current session ID is: {0}", OperationContext.Current.SessionId);
this._nCount++;
} public int GetResult()
{
Console.WriteLine("The GetResult method is invoked and the current session ID is: {0}", OperationContext.Current.SessionId);
return this._nCount;
}
#endregion
}

  此时运行服务宿主的输出结果如下图所示:

  从运行结果可以看出,对于Single方式,服务实例在服务类型被寄宿的时候就已经创建了,对于PerCall和PerSession方式而是在通过Proxy调用相应的服务操作之后,服务实例才开始创建的。下面运行客户端程序,你将看到如下图所示的运行结果:

  此时,第二个Proxy返回的结果是4而不是2,这是因为采用Single方式只存在一个服务实例,所有的调用状态都将保留,所以_nCount的值在原来的基础上继续累加。

四、总结

  到这里,本文的分享就结束了,本文主要分享了WCF中实例管理的实现。从WCF的实例实现可以看出,WCF实例实现是借鉴了.NET Remoting中实例实现,然后分别分享了服务实例三种激活方式在WCF中的实现,并通过对运行结果进行对比来让大家理解它们之间的区别。

  本文所以源码:WCFInstanceManager.zip

跟我一起学WCF(8)——WCF中Session、实例管理详解的更多相关文章

  1. php中session机制的详解

    [补充]session_start()要放在php最前面,header()函数也要放在session_start()之后. [读了下面的文章转载的文章后自己的理解]: 1,通过phpinfo()函数可 ...

  2. Spring中的事务管理详解

    在这里主要介绍Spring对事务管理的一些理论知识,实战方面参考上一篇博文: http://www.cnblogs.com/longshiyVip/p/5061547.html 1. 事务简介: 事务 ...

  3. thinkPHP中session()方法用法详解

    本文实例讲述了thinkPHP中session()方法用法.分享给大家供大家参考,具体如下: 系统提供了Session管理和操作的完善支持,全部操作可以通过一个内置的session函数完成. 用法 ? ...

  4. MemCache中的内存管理详解

    MC的内存管理机制 1.内存的碎片化 当我们使用C语言或者其他语言进行malloc(申请内存),free(释放内存)等类似的命令操作内存的时候, 在不断的申请和释放的过程中,形成了一些很小的内存片段, ...

  5. php中Session使用方法详解

    Session的声明与使用 Session的设置不同于Cookie,必须先启动,在PHP中必须调用session_start().session_start()函数的语法格式如下: Bool sess ...

  6. 跟着阿里p7一起学java高并发 - 第19天:JUC中的Executor框架详解1,全面掌握java并发核心技术

    这是java高并发系列第19篇文章. 本文主要内容 介绍Executor框架相关内容 介绍Executor 介绍ExecutorService 介绍线程池ThreadPoolExecutor及案例 介 ...

  7. Asp.net中web.config配置文件详解(一)

    本文摘自Asp.net中web.config配置文件详解 web.config是一个XML文件,用来储存Asp.NET Web应用程序的配置信息,包括数据库连接字符.身份安全验证等,可以出现在Asp. ...

  8. JavaScript中return的用法详解

    JavaScript中return的用法详解 最近,跟身边学前端的朋友了解,有很多人对函数中的this的用法和指向问题比较模糊,这里写一篇博客跟大家一起探讨一下this的用法和指向性问题. 1定义 t ...

  9. 【转载】C# 中的委托和事件(详解:简单易懂的讲解)

    本文转载自http://www.cnblogs.com/SkySoot/archive/2012/04/05/2433639.html C# 中的委托和事件(详解) C# 中的委托和事件 委托和事件在 ...

随机推荐

  1. 退出系统时跳出frame框架

    传统的系统界面,有iframe页面,当用户退出系统或者session过期或者非法请求时,都要使当前页面跳转到登录页面.比如用户点击注销的按钮在上面得top.jsp里面,方法:<a href=&q ...

  2. Hadoop MapReduce编程 API入门系列之薪水统计(三十一)

    不多说,直接上代码. 代码 package zhouls.bigdata.myMapReduce.SalaryCount; import java.io.IOException; import jav ...

  3. 设置PATH变量

    一不小心把PATH变量清空了,所有的命令都执行不了了,提示“xxx: command not found”,解决办法:在命令行输入export PATH=/usr/local/sbin:/usr/lo ...

  4. POJ 2155 Matrix (二维线段树)

    Matrix Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 17226   Accepted: 6461 Descripti ...

  5. eclipse的ssh框架详解

    1.创建项目 2.导包 1.1:导入Struts2可能用到的包: 先从网站下载 再这里找出,打开它把WEB-INF/lib/下的所有包导入项目   1.2:导入spring可能用到的包: 先从网站下载 ...

  6. HttpURLConnection请求网络数据的Post请求

    //--------全局变量----------- //注册Url    private String urlPath="http://101.200.142.201:8080/VideoP ...

  7. javascript学习第二课

    主要内容: 1.不可变的原始值和可变的对象引用 javascript中的原始值(undefined.null.布尔值.数字和字符串)与对象(包括数组和函数)有着根本的区别.原始值是不可更改的;任何方法 ...

  8. 简单介绍智能DNS解析+双线路接入

    导读:在讨论这个问题,其中群友老孤同志也提供了不少非常有参考价值的资料,所以我们再把这些资料再整理一次,从比较底层的技术原理上重新进行一次分析.   我们知道,因为南电信北网通现象的存在,我们的服务器 ...

  9. opps kio

    Unable to handle kernel NULL pointer dereference at virtual address 00000008pgd = c7090000, hw pgd = ...

  10. php使用openssl来实现RSA(非对称加密)

    使用非对称加密主要是借助openssl的公钥和私钥,用公钥加密私钥解密,或者私钥加密公钥解密. 1.安装openssl和PHP的openssl扩展 2.生成私钥:openssl genrsa 用于生成 ...