下面我们来进入详细的问题讨论,首先是问题的来源。
【1】问题来源:
这个问题来自于MSDN中文技术论坛,闲置很久,yangjian15 提出的WCF一个疑惑?,原帖地址:
http://social.microsoft.com/Forums/zh-CN/wcfzhchs/thread/c990c287-c422-4550-9501-c04395d9ea15
内容是:建立如下wcf服务 ,采用tcpbinding:
契约:
[ServiceContract ]
public interface Iservice
{
[OperationContract ]
string getstr(string str);
}
实现:
[ServiceBehavior (InstanceContextMode=InstanceContextMode.PerCall )]
public class servicetest:Iservice
{
public string getstr(string str)
{
Thread.Sleep(3000);
return str;
}
}
用两个客户端同时访问getstr, 结果是一个客户端3秒返回 ,一个客户端6秒。测试过是产生的两个实例,但这两个实例好像运行在一个线程上。如果是这样,wcf的性能就有很大问题。
各位大师帮我看看 这是什么原因 ?怎么让两个客户端同时返回,即这两个实例运行在不同线程上??
【2】资料收集:
我在网上查看了一下资料,其实也没搜索到这个问题特定的讨论或者文章。其实分析这个问题,更好应该参考服务激活类型之类的文章。基本的资料就是:WCF分布式开发步步为赢(9):WCF服务实例激活类型编程与开发和《WCF服务编程》里关于激活的章节。大家也可以搜索相关的技术文章。作为参考。这个问题直接很难找到准确的的答案,需要从服务实例的类型入手。
【3】示例代码:
我们这里的测试代码涵盖了3中类型的服务。提问帖子讨论的是默认的VS2008创建的服务,这个WCF服务如果不指定类型的话,会使用会话服务。我们这里为了测试的准确和严密,分别对单调服务(CallService),会话服务(Sessionful Service),单例服务(SingletonService)坐了测试,每次操作延迟5秒,模拟服务处理费时。下面分别给出代码:
【3.1】服务端:
这里我们分别配置了单调服务(Call Service),会话服务(SessionfulService),单例服务(Singleton Service)三种不同激活类型的服务。具体代码如下:
//1.服务契约
[ServiceContract(SessionMode=SessionMode.Allowed, Namespace = http://www.cnblogs.com/frank_xl/)]
public interface IWCFService
{
//操作契约
[OperationContract]
void SayHello();
}
//2.服务类.单调服务
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]
public class WCFServicePerCall : IWCFService,IDisposable
{
//服务实例计数
private Guid ServiceGUID;
//构造函数
public WCFServicePerCall()
{
ServiceGUID = Guid.NewGuid();
Console.WriteLine(WCFServicePerCall{0} is Created at: + DateTime.Now.ToLongTimeString(),ServiceGUID);
}
//实现接口定义的方法
public void SayHello()
{
Console.WriteLine( WCFServicePerCall SayHello at:{0}
Thread.Sleep(5000);
}
//实现接口定义的方法Dispose
public void Dispose()
{
Console.WriteLine(WCFServicePerCall{0} is disposed at: + DateTime.Now.ToLongTimeString(), ServiceGUID);
}
}
//3.服务类.会话模式
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class WCFServicePerSession : IWCFService, IDisposable
{
//服务实例计数
private Guid ServiceGUID;
//构造函数
public WCFServicePerSession()
{
ServiceGUID = Guid.NewGuid();
Console.WriteLine(WCFServicePerSession{0} is Created at: + DateTime.Now.ToLongTimeString(), ServiceGUID);
}
//实现接口定义的方法
public void SayHello()
{
Console.WriteLine( WCFServicePerSession SayHello at:{0}
Thread.Sleep(5000);
}
//实现接口定义的方法Dispose
public void Dispose()
{
Console.WriteLine(WCFServicePerSession{0} is disposed at: + DateTime.Now.ToLongTimeString(), ServiceGUID);
}
}
//4.服务类.单件、单例模式
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class WCFServiceSingle : IWCFService, IDisposable
{
//服务实例计数
private Guid ServiceGUID;
//构造函数
public WCFServiceSingle()
{
ServiceGUID = Guid.NewGuid();
Console.WriteLine(WCFServiceSingle{0} is Created at: + DateTime.Now.ToLongTimeString(), ServiceGUID);
}
//实现接口定义的方法
public void SayHello()
{
Console.WriteLine( WCFServiceSingle SayHello at:{0}
Thread.Sleep(5000);
}
//实现接口定义的方法Dispose
public void Dispose()
{
Console.WriteLine(WCFServiceSingle{0} is disposed at: + DateTime.Now.ToLongTimeString(), ServiceGUID);
}
}
【3.2】宿主:分别配置三种类型的服务,这里不在详述,基本服务的配置大家都应该了解了,如果有疑问可以参考WCF分布式开发步步为赢(2)自定义托管宿主WCF解决方案开发配置过程详解。下面是配置文件的代码:
Code
services
service behaviorConfiguration=WCFService.WCFServiceBehavior name=WCFService.WCFServicePerCall
endpoint
address=net.tcp://localhost:9001/WCFServicePerCall
binding=netTcpBinding
contract=WCFService.IWCFService
/endpoint
endpoint address=mex binding=mexTcpBinding contract=IMetadataExchange /
host
baseAddresses
add baseAddress=net.tcp://localhost:9001//
/baseAddresses
/host
/service
service behaviorConfiguration=WCFService.WCFServiceBehavior name=WCFService.WCFServicePerSession
endpoint
address=net.tcp://localhost:9002/WCFServicePerSession
binding=netTcpBinding
contract=WCFService.IWCFService
/endpoint
endpoint address=mex binding=mexTcpBinding contract=IMetadataExchange /
host
baseAddresses
add baseAddress=net.tcp://localhost:9002//
/baseAddresses
/host
/service
service behaviorConfiguration=WCFService.WCFServiceBehavior name=WCFService.WCFServiceSingle
endpoint
address=net.tcp://localhost:9003/WCFServiceSingle
binding=netTcpBinding
contract=WCFService.IWCFService
/endpoint
endpoint address=mex binding=mexTcpBinding contract=IMetadataExchange /
host
baseAddresses
add baseAddress=net.tcp://localhost:9003//
/baseAddresses
/host
/service
/services
【3.3】客户端:客户端添加服务引用的方式,生成本地类和配置文件。这里的测试使用分别实例化客户端代理,一次两个客户端,同事调用单调服务、会话服务、单例服务,看看宿主的输出信息。服务Thread.sleep(5000),进程休眠5秒。来打印消息,对比结果,下面是客户端代码:
public void WCFClientPerCall()
{
#region 1.单调服务代理
using (ServiceReferencePerCall.WCFServiceClient WCFServicePerCallProxy = new ServiceReferencePerCall.WCFServiceClient())
{
//调用服务,每次服务需要5秒执行时间
Console.WriteLine( WCFServicePerCallProxy calls at:{0}
WCFServicePerCallProxy.SayHello();
}
#endregion
}
public void WCFClinetPerSession()
{
#region 2.会话服务代理
using (ServiceReferencePerSessi
{
//调用服务,每次服务需要5秒执行时间
Console.WriteLine( WCFServicePerSessionProx
WCFServicePerSessionProx
}
#endregion
}
public void WCFClientSingle()
{
#region 3.单例服务代理
using (ServiceReferenceSingle.WCFServiceClient WCFServiceSingleProxy = new ServiceReferenceSingle.WCFServiceClient())
{
//调用服务,每次服务需要5秒执行时间
Console.WriteLine( WCFServiceSingleProxy calls at:{0}
WCFServiceSingleProxy.SayHello();
}
#endregion
}
【3.4】测试结果:
测试的结果依次是分别截图如下:
(1)单调服务(Call Service):
< p>
(2)会话服务(Sessionful Service):
< p>
(3)单例服务(Singleton Service):
< p>
【4】总结:
以上就是全部分分析和测试代码,由于手动操作测试的客户端,开始时间有差别,不过服务时间响应后移,对比结果的时候不影响。这里调用时间都做了打印输出,大家可以再图中看出.一下几点是值得注意的地方:
(1)WCF服务实例激活类型包括三种方式:单调服务(Call Service),会话服务(SessionfulService),单例服务(Singleton Service).
(2)服务实例的默认激活方式为会话服务模式。
(3)针对三种方式,应该说单调服务(Call Service),会话服务(SessionfulService)不需要客户端等待,可以分别响应客户端请求。单例服务(SingletonService).服务需要依次响应客户端的请求。
(4)服务并发模式对客户端请求处理也有影响,当服务的并发模式设置为ConcurrencyMode.Multiple,客户端请求不会放入WCF消息队列,会直接转发给WCF服务实例处理,此时不会等待。默认的最大并发调用数字16,而且限流对此也有影响,大家处理的时候应该注意。
(5)服务的并发模式默认的情况是ConcurrencyMode.Single.客户端的消息会被放入WCF消息队列。等待服务的处理,此时如果长时间处理请求,可能导致连接出错。 所以这个是针对原有问题的正确和全面的答案~