(5):Binding细解
本文的出发点:
通过阅读本文,您能了解以下知识:
- WCF中的Binding是什么?
- Binding的组成?
- Binding Element 的分类?
- Binding描述了那些层面的信息?
- 选择正确的Binding
本文适合的读者:
本文适合WCF的初学者
WCF中的Binding是什么?
在弄清Binding是什么之前,我们先看一下WCF的中的Endpoint结构,一个Endpoint就类似于xml
web service中的一个asmx文件,它包括三个组成部分1)Address 2) Binding
3) Contract,此三者被称为组成Endpoint的"ABC",下图阐述了Endpoint的组成结构:
ABC分别解决了Endpoint的如下问题:
A解决了Endpoint在哪的问题,也就是"Where is the endpoint?"
B 解决的是如何与Endpoint通讯的问题,也就是"How to communicate
with endpoint?"
C解决了Endpoint提供什么功能的问题,也就是"What functionalities
do the endpoint provide?"
上面的两篇文章WCF从理论到实践三:八号当铺之黑色契约和WCF从理论到实践四:路在何方分别阐述了Contract和Address,本文着重讨论Binding,
Binding实现的是在客户端和服务端进行通讯的底层细节,包括数据如何传输(比如采用Tcp,Http等),如何解决安全问题(SSL,Message
Level Security等),传输的消息的格式(如text/xml,MTOM,binary等)。
Binding的组成?
Binding包括三个组成部分:NameSpace,Name和BindingElement,如下图所示:
Name和NameSpace是服务元数据(meta date)的唯一标志,换句话说,二者就像Binding的姓名一样,而BindingElement则描述Binding的特征,我们说Binding能解决通讯方式的问题,关键是靠BindingElement来进行描述。
Binding Element 的分类
Binding Element中最核心的组成部分,它分为以下三种类型
- Encoding Binding Element:它用于描述传输数据的编码方式,比如用text/xml进行编码,用binary编码,用MTOM进行编码都是在这个上面定义,每个Binding
Element必须包含一个Encoding Binding Element。
- Transport Binding Element:它用于描述数据的传输方式,例如使用tcp进行传输,还是用http进行传输,还是用msmq,这些都是由Transport
Binding Element来定义,每一个Binding Element 必须包含一个Transport
Binding Element
- Protocol Binding Element: 指定诸如安全性、可靠性、上下文流设置(context
flow settins)
Binding描述了哪些层面的信息
一个Binding包含着丰富的信息,每种信息都描述了服务端和客户端交互过程中的一方面,如下表所示,Binding描述了这些层面的信息:
层次 |
备注说明 |
Transactions(事务) |
TransactionFlowBindingElement,用于指定事务流程 |
Reliability(信赖) |
ReliableSessionBindingElement,用于指定对会话方式 |
Security(安全) |
SecurityBindingElement,指定安全方式 |
Encoding(编码) |
Text, Binary, MTOM, Custom,指定数据传输格式 |
Transport(传输) |
TCP, Named Pipes, HTTP, HTTPS, MSMQ, Custom,指定传输方式 |
选择正确的Binding
Binding是可以自定义实现的,可WCF框架已经为我们实现了足够多的Binding,供我们来选择,如下表所示:
Binding名称 |
Configuration Element
|
描述 |
BasicHttpBinding |
basicHttpBinding |
一个指定用符合基本网络服务规范通讯的binding,它用http进行传输,数据格式为text/xml |
WSHttpBinding |
wsHttpBinding |
一个安全的通用的binding,但它不能在deplex中使用 |
WSDualHttpBinding |
wsDualHttpBinding |
一个安全的通用的binding,但能在deplex中使用 |
WSFederationHttpBinding |
wsFederationHttpBinding |
一个安全的通用的支持WSF的binding,能对用户进行验证和授权 |
NetTcpBinding |
netTcpBinding |
在wcf应用程序中最适合跨机器进行安全通讯的binding |
NetNamedPipeBinding |
netNamedPipeBinding |
在wcf应用程序中最适合本机进行安全通讯的binding |
NetMsmqBinding |
netMsmqBinding |
在wcf应用程序中最适合跨机器进行安全通讯的binding,并且支持排队 |
NetPeerTcpBinding |
netPeerTcpBinding |
一个支持安全的,多机交互的binding |
MsmqIntegrationBinding |
msmqIntegrationBinding |
一个用于wcf与现有msmq程序进行安全通讯的binding |
下面的表格分别删除了上表中的Binding在互操作性(Interoperability), 安全性(Security),
是否支持会话(Session), 是否支持事务(Transactions)和是否为全双工(Duplex)上不同。
Bingding |
Interoperability |
Security |
Session |
Transactions |
Duplex |
BasicHttpBinding |
Basic Profile 1.1 |
(None), Transport, Message |
None, (None) |
None |
n/a |
WSHttpBinding |
WS |
Transport, (Message), Mixed |
(None), Transport, Reliable Session |
(None), Yes |
n/a |
WSDualHttpBinding |
WS |
(Message) |
(Reliable Session) |
(None), Yes |
Yes |
WSFederationHttpBinding |
WS-Federation |
(Message) |
(None), Reliable Session |
(None), Yes |
No |
NetTcpBinding |
.NET |
(Transport), Message |
Reliable Session, (Transport) |
(None), Yes |
Yes |
NetNamedPipeBinding |
.NET |
(Transport) |
None, (Transport) |
(None), Yes |
Yes |
NetMsmqBinding |
.NET |
Message, (Transport), Both |
(None) |
(None), Yes |
No |
NetPeerTcpBinding |
Peer |
(Transport) |
(None) |
(None) |
Yes |
MsmqIntegrationBinding |
MSMQ |
(Transport) |
(None) |
(None), Yes |
n/a |
例外,《Programming WCF Services》有一幅图也能说明各自的特征:
下面的图给出了我们选择Binding的方式
(6):WCF架构
前面的几篇文章,分别介绍了WCF的基本知识和Endpoint的三个重要组成部分:Address,Contract,Binding。但无非管中窥豹而已,本文就阐述一下WCF的架构,从整体的角度来重新对WCF加深认识。
本文的出发点:
通过阅读本文,能使您获得以下知识:
- WCF的架构图
- WCF架构的关键元素及其概念
- 创建一示例程序,并对其按架构图进行解析
本文适合的读者:
本文适合WCF初学者,以前可以对WCF一无所知,本文只作介绍,不涉及WCF技术具体技术难点和介绍。
WCF的架构图
下图为WCF的架构图:
WCF架构的关键元素及其概念
Contracts and Description (协定和说明):
协定定义消息系统的各个方面。包括Data Contract(数据协定),Message Contract(消息协定)
,Service Contract(服务协定)。Data Contract是WCF中 Service与Client端之间用来交换的数据的格式定义,它采用Xml
架构定义语言(XSD)来定义,使得服务端和客户端都能理解数据格式定义。消息协定能定义消息的特定部分,默认情况下
,WCF的消息是由固定格式的,但某些情况下,也可以使用Message Contrace来自定义消息格式。服务协定指定服务端公开的方法签名,这些方法能够在远程被调用。举个国家和间谍的例子,A
是国家C派往其它国家的一名间谍,他负责收集情报,被通过某种特定的渠道,将获得的情报发送给国家C,假如A在出去执行任务之前,国家C的特工部门为其编订了一套密文,这个密文只有国家C和特工部门和A知道具体含义,这些密文正是在A与其国家之间进行传递的数据,国家C对密文的制定,正是WCF中对交换数据格式的定义,属于Data
Contract范畴,而A在获得情报之后,通过电报的方式将情报发回给国家情报机关,它首先设置电报的发送地址并注明来源,并且将情报信息编码成密文进行发送,对电报报文的格式定义就属于Message
Contract范畴,而国家情报机关为A要想接受和对A发送任务命令,必须也提供固定的通讯设施和人,这些能够被A所联络上的通讯设施和人就属于Service
Contract的范畴。
Service Runtime(服务运行时)
上面说的协定是在开发wcf服务时候制定的,而服务运行时,是在服务实际运行时候地一些行为控制。ErrorBehavior是在服务出现错误的时候发生的操作,ThrottingBehavior能够限制创建多少个实例和会话,通过这个您能够控制WCF服务的性能,MetaBehavior(元数据行为)控制是否和如何向外部提供元数据。TransactionBehavior能定义事务性,使得当发生异常的时候能进行回滚。DispatchBehavior(调度行为)能控制WCF处理消息的方式,通过扩展性功能可以自定义运行时进程。
例如,消息检查功能用于检查消息的各个部分,使用参数筛选功能可以根据作用于消息头的筛选器来执行预设操作。下图显示了WCF的消息处理流程
Messaging(消息传递)
在WCF中,消息是在通道(Channel)中进行传递的。通道是以某种方式对消息进行处理的组件 。一组通道可以组合成"通道堆栈",主要有两大种通道:协议通道和传输通道。协议通道说明数据的格式和交换模式,WS-Security
是对在消息层启用安全性的 WS-Security 规范的实现。 通过 WS-Reliable Messaging
通道可以保证消息的传递。 编码器提供了大量的编码,可使用这些编码来满足消息的需要。 HTTP 通道指定应使用超文本传输协议来传递消息。
同理,TCP 通道指定 TCP 协议。 事务流通道控制已经过事务处理的消息模式。 通过命名管道通道可以进行进程间通信。
使用 MSMQ 通道可以与 MSMQ 应用程序进行互操作。
Activation and Hosting(激活和承载)
WCF服务的最终形式仍然是程序,它能够"自承载",也可以寄宿到其它应用程序之中 ,如IIS,Windows激活服务,Com+等。
创建一示例程序,并对其按架构图进行解析
创建一个最简单的WCF服务,并利用上述的架构对其解析
打开vs2008,创建一个Wcf Application,命名为WcfSample1 ,如下图所示:
我们就利用系统模板产生的文件来进行解析,完成上步中,会自动产生如下的项目:
打开IService.cs,
为了简单的阐述MessageContract,我们新建一个CustomMessage.cs,代码如下:
为使用该服务,我们再创建一个ServiceContract接口和其具体实现,分别为:IMessagingHello.cs和MessageHello.svc
和
在web.config作如下的配置:
创建一个客户端应用项目Client用于消费上面创建的服务,创建项目后,添加Service引用,如下图所示:
分别添加对MessageHello.svc和Service1.svc的引用后,在programe.cs中添加如下代码:
用tcpTrace来监听自定义报文,可以得到
发送的消息报文为:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><HelloGreetingMessage
xmlns="http://tempuri.org/"><Salutations
xmlns="http://jillzhang.cnblogs.com">jillzhang</Salutations></HelloGreetingMessage></s:Body></s:Envelope>
而接受到消息报文为:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Header><h:OutOfBandData
s:mustUnderstand="1"
xmlns:h="http://jillzhang.cnblogs.com">Served
by object 63334862.</h:OutOfBandData></s:Header>
<s:Body><HelloResponseMessage xmlns="http://tempuri.org/"><ResponseToGreeting
xmlns="http://jillzhang.cnblogs.com">Service
received: jillzhang</ResponseToGreeting></HelloResponseMessage></s:Body></s:Envelope>
从而可知MessageContract已经起了作用。
有关如何用tcpTrace监视消息报文,参见:http://www.cnblogs.com/artech/archive/2007/06/14/782845.html
本文参考文章
- http://www.cnblogs.com/artech/archive/2007/06/14/782845.html
- http://msdn2.microsoft.com/en-us/library/ms733128.aspx
本文示例项目
(7):消息交换模式
本文的出发点
通过阅读本文,您能理解以下知识:
- WCF定义了哪几种消息交换模式?
- One-Way Calls
- Request/Reply
- Duplex
- 用示例来解析WCF的消息交换模式
本文适合的读者
本文涉及到了SOA中的消息交换的基础概念,需要一些初级的Xml Web Service和分布式系统开发的经验,最好理解WCF架构
WCF定义了哪几种消息交换模式?
WCF定义了三种消息交换方式 ,分别为:
- One-Way Calls
- Request/Reply
- Duplex
One-Way Calls
在几种消息交换模式中,one-way calls是最没良心的,对于客户端,one-way calls就如肉包子打狗,有去无回。下面的图示给出这种交换模型的特征:
在这种交换模式中,存在着如下的特征
- 没有返回值,返回类型只能为void
- 不能包含ref或者out类型的参数
- 只有客户端发起请求,服务端并不会对请求进行回复。
通过设置OperationContract的IsOneWay=True可以将满足要求的方法设置为这种消息交换模式,方法如下:
[OperationContract(IsOneWay=true)]
void
Test(int intVal);
上面的代码,就是将方法Test设置成为了one-way call的消息交换模式,注意如果Test方法的返回类型不是void或者带有ref或者out类型的参数,都会抛出异常InvalidOperationException,如下面列表中的方法均不能被声明为one-way模式
int
Test(int intVal);
int
Test();
int
Test();
void
Test(ref int
intVal);
void
Test(out int
intVal);
Request/Reply
request/reply比起one-way来说,就更懂得礼尚往来,它是缺省的消息交换模式,类似于http协议中的请求/响应模型。下面的图示给出这种交换模式的特征:
这种交换模式是使用最多的一中,它有如下特征:
- 调用服务方法后需要等待服务的消息返回,即便该方法返回 void 类型
- 相比Duplex来讲,这种模式强调的是客户端的被动接受,也就是说客户端接受到响应后,消息交换就结束了。
- 在这种模式下,服务端永远是服务端,客户端就是客户端,职责分明。
它是缺省的消息交换模式,设置OperationContract便可以设置为此种消息交换模式
[OperationContrac]
void
Test(int intVal);
注意,尽管Test方法返回为void,但Server也会生成reply响应并发送给client.有来有往是这种模式的特征。
Duplex
这种交换模式比起上面两种,比较复杂,它和request/reply模式类似,也是有来有往,但处理过程却比request/reply要复杂,因为它可以在处理完请求之后,通过请求客户端中的回调进行响应操作,这种模式的图示为:
注意,这种方式和request/reply方式的图示也很类似,当二者存在着至关重要的不同,它在客户端也有监听节点,在callback的时候,服务器和客户端的角色会进行交换,服务端此时成了严格意义上的客户端,而客户端此时能接受服务端的callback请求,所以成为了服务端。呵呵,辩证法,都拗口死了,当事实就是这种,就像对与错一样,会相互转换,失败是成功之母,而成功是失败之源。废话少说,Duplex的特征主要包括
- 消息交换过程中,服务端和客户端角色会发生调换
- 服务端处理完请求后,返回给客户端的不是reply,而是callback请求。
打个比方,Reqeust/Reply方式像是搓澡,1个管搓,1个被搓
而duplex像是拳击,两个人都会出拳
Duplex模式对Bindding有特殊的要求,它要求支持Duplex MEP(Message Exchange
Pattern),如WSDualHttpBinding和NetTcpBinding,有关Binding的介绍请参见http://www.cnblogs.com/jillzhang/archive/2008/02/03/1063406.html
用示例来解析WCF的消息交换模式
建立示例的步骤不做具体阐述,下面看一下项目的最终结构:
下表说明各个项目的作用
项目名称 |
项目作用 |
包含文件 |
Jillzhang.Messaging.Contract |
定义WCF服务端和客户端共同使用的Contract接口 |
IOneWayJob.cs
INormalJob.cs
IJob.cs
ICallback.cs |
Jillzhang.Messaging.Service |
实现WCF服务的Contract |
OneWayJob.cs
NormalJob.cs
Job.cs |
Jillzhang.Messaging.Host |
一个Console应用程序,用于承载WCF服务端 |
Program.cs
App.config |
Jillzhang.Messaging.WebSite |
一个用于WebSite,用于承载WCF服务。是例外一中Host |
OnewayService.svc
NormalJobService.svc
JobService.svc
web.config |
Jillzhang.Messaging.Client |
WCF客户端,一个Console应用程序 |
OnewayProxy.cs
NormalJobProxy.cs
DuplexProxy.cs
MyCallback.cs
Program.cs
app.config |
下面就看下如何定义消息交换模式为one-way的Contract接口
而IOneWayJob的实现类代码为:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jillzhang.Messaging.Contract;
namespace Jillzhang.Messaging.Service
{
public class OneWayJob : IOneWayJob
{
public void Do(string jobName)
{
System.Diagnostics.Stopwatch watcher = new System.Diagnostics.Stopwatch();
watcher.Start();
System.Threading.Thread.Sleep(1000);
Console.WriteLine("服务" + AppDomain.CurrentDomain.FriendlyName + "执行任务:" + jobName);
watcher.Stop();
}
}
}
Request/reply的Contract接口定义如下:
而INormalJob的实现代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jillzhang.Messaging.Contract;
namespace Jillzhang.Messaging.Service
{
public class NormalJob:INormalJob
{
public string Do(string jobName)
{
try
{
System.Diagnostics.Stopwatch watcher = new System.Diagnostics.Stopwatch();
watcher.Start();
System.Threading.Thread.Sleep(1000);
Console.WriteLine("服务" + AppDomain.CurrentDomain.FriendlyName + "执行任务:" + jobName);
watcher.Stop();
return "成功";
}
catch
{
return "失败";
}
}
}
}
Duplex的交换模式需要现定义Callback的Contract接口,如下:
而服务端的Contract接口为:
Duplex的Contract实现为:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jillzhang.Messaging.Contract;
using System.ServiceModel;
namespace Jillzhang.Messaging.Service
{
[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Multiple)]
public class Job:IJob
{
public string Do(string jobName)
{
try
{
ICallback callback = OperationContext.Current.GetCallbackChannel<ICallback>();
System.Diagnostics.Stopwatch watcher = new System.Diagnostics.Stopwatch();
watcher.Start();
System.Threading.Thread.Sleep(1000);
Console.WriteLine("服务" + AppDomain.CurrentDomain.FriendlyName + "执行任务:" + jobName);
watcher.Stop();
callback.Done((int)watcher.ElapsedMilliseconds);
return "成功";
}
catch
{
return "失败";
}
}
}
}
下面,我们来看一下,如何创建承载服务的应用程序,首先在app.config做如下配置
而Host的代码如下:
而客户端的配置文件,如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="netTcpBinding" />
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://localhost:6987/Service/duplex" binding="netTcpBinding"
bindingConfiguration="netTcpBinding" contract="Jillzhang.Messaging.Contract.IJob"
name="NetTcpBinding">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="net.tcp://localhost:6987/Service/oneway" binding="netTcpBinding"
bindingConfiguration="netTcpBinding" contract="Jillzhang.Messaging.Contract.IOneWayJob"
name="NetTcpBinding">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="net.tcp://localhost:6987/Service/normal" binding="netTcpBinding"
bindingConfiguration="netTcpBinding" contract="Jillzhang.Messaging.Contract.INormalJob"
name="NetTcpBinding">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>
需要注意的是:在设定Duplex模式时,如果服务端采用的是WsDualHttpBinding,而不是本文中的NetTcpBinding,最好指定以下clientBaseAddress,默认情况下,clientBaseAddress会尝试用80端口,可通常情况80端口都是被占用,你需要设置一个其他端口。
因为回调的Contract实现是在客户端的,所以需要在客户端实现1个ICallback实现,代码如下:
下面是客户端调用的代码:
客户端代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jillzhang.Messaging.Contract;
namespace Jillzhang.Messaging.Client
{
class Program
{
static void Main(string[] args)
{
Program p = new Program();
p.OneWayCall();
p.NormalCall();
p.DuplexCall();
Console.Read();
}
void DuplexCall()
{
try
{
MyCallback callback = new MyCallback();
IJob ws = new JobClient(new System.ServiceModel.InstanceContext(callback));
Console.WriteLine("--------------------Duplex Calls ---------------------------");
Console.WriteLine("开始调用服务");
string result = ws.Do("duplex job");
Console.WriteLine("收到返回信息:" + result);
Console.WriteLine("-------------------------------------------------------------");
Console.WriteLine("\r\n\r\n\r\n");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
void OneWayCall()
{
try
{
Console.WriteLine("-----------------------One-Way Calls-----------------------");
IOneWayJob ws = new OneWayJobClient();
ws.Do("one-way job");
Console.WriteLine("请求完成!");
Console.WriteLine("-------------------------------------------------------------");
Console.WriteLine("\r\n\r\n\r\n");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
void NormalCall()
{
try
{
Console.WriteLine("-----------------------Request/Reply Calls-----------------------");
INormalJob ws = new NormalJobClient();
string result = ws.Do("request/reply job");
Console.WriteLine("请求完成,返回结果:"+result);
Console.WriteLine("-------------------------------------------------------------");
Console.WriteLine("\r\n\r\n\r\n");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
首先运行服务承载程序Jillzhang.Messaging.Host,然后运行客户端
会产生如下的结果:
服务端运行解图
客户端运行解图:
本文参考资料
-
http://msdn.microsoft.com/msdnmag/issues/06/10/wcfessentials/default.aspx
- http://www.rainsts.net/article.asp?id=428
本文相关示例文件
示例项目:/Files/jillzhang/Jillzhang.Messaging.rar
(8):事件广播
上文讨论了WCF中三种消息交换模式,one-way,request/reply,duplex。前两项比较简单,无需多言,duplex相对比较复杂,上文只是实现了简单的回调,在真正应用的时候,还有许多值得注意之处,本文就结合一个实际的应用例子来谈论下duplex的具体应用和非常值得我们注意的地方。
本文的出发点
通过阅读本文,您能理解以下知识:
- 如何实现一个基于duplex的事件广播
- 解析在实现duplex事件广播中的几个问题
- 初步探讨一下异步
本文适合的读者
本文属于中等难度的文章,需要有WCF消息交换和windows应用程序开发相关的基础知识,有关WCF消息交换,请阅读http://www.cnblogs.com/jillzhang/archive/2008/02/17/1071521.html
如何实现一个基于duplex的事件广播
在讨论如何实现之前,先看一下本文的范例所要实现的功能是什么?本文的范例实现了一个简单的分布式任务管理系统,简单的说,它是在服务端(Server
Point)执行任务(Job),并且将任务的信息呈现给客户端。它有如下特征:
- 通过调用服务端的Accept(),客户端能连接上服务端,并保持会话。
- 客户端在启动的时候,可以通过远程调用GetJobs()来获取当前服务端中全部的任务,并将这些任务在客户端窗体中用列表控件呈现出来
- 客户端能通过调用AddJob()向服务端添加任务,当服务端完成添加操作之后,引发添加完成的事件,并向全部的客户端广播该事件
- 当客户端服务端发来的添加新任务事件广播的时候,客户端将新增任务添加到列表控件加以呈现
- 客户端可以命令服务端执行具体某个任务,当任务在开始执行和执行结束后,服务端都会像全部客户端广播任务的执行情况,并且任务的执行和事件的广播异步执行
- 客户端收到广播后,便可以更新任务信息。
和以前文章不同,本文先给出最后实现的效果
如何您要了解该范例得具体设计和实现,可以下载下面的文件进行分析:
范例最终实现:/Files/jillzhang/Jillzhang.Event.rar
我这里只列出范例中项目列表
项目名称 |
项目描述 |
Jillzhang.Event.Core |
该项目用于定义WCF的契约,主要包括IServer服务契约,ICallback用于回调的服务契约,Job数据契约 |
Jillzhang.Event.Service |
服务端的具体实现,其中Server实现了一个有广播事件能力的服务契约 |
Jillzhang.Event.Host |
服务的宿主程序,一个ConsoleApplication |
Jillzhang.Event.Client |
客户端实现,用于消费服务端。 |
Jillzhang.Event.Client2 |
和Jillzhang.Event.Client是一个实现,但为了验证广播,可与Jillzhang.Event.Client同时消费服务端 |
解析在实现duplex事件广播中的几个问题
1) Duplex模式对服务行为ConcurrencyMode的要求
我们知道ConcurrencyMode是控制服务并发的,默认情况下ConCurrendMode的值为Single,它设置服务运行在单线程下,当上一个请求未完成之前,服务是不接受下一个请求的。而duplex在进行回调的时候,如果回调方法没有被设置为
One-Way的交换模式,服务端是会等待客户端对回调的响应的,这可不是一件好事情,因为服务端并不能保证客户端能正常地执行回调并返回数据。更多的情况下,我们期望回调在发出后能立即返回,方法有两个:a)将回调方法设置为One-Way交换模式
b)采用多线程。经过我的测试,当回调方法被设置了one-way模式后,将ConcurrencyMode设置为Single是可以实现duplex双向通讯的。
要第二种方法也非常简单,只需要将ConcurrencyMode设置为Mutiple.此时即使回调方法不是one-way模式,也是可以完成duplex的。值得说明一下的是ConcurrencyMode还有中性的属性:ConcurrencyMode.Reentrant,说句心里话,我不喜欢这个不伦不类的家伙,他能实现在单线程下同时接受多个请求,但有利必有弊,这个家伙不能保证请求事务的完整性,使用的时候应该谨慎。
2)InstanceContextMode
= InstanceContextMode.PerSession却为何能实现广播?
如果将InstanceContextMode设置为PerSession,我们知道服务端对象是针对每一个会话的,也就是说每个会话会产生一个对象实例,这样如果要实现广播,我们必须将当前服务包含的会话信息用一个列表对象记录下来,广播的时候,我们遍历会话列表,进行逐个回调。本示例中巧妙的利用了Event可包括多个委托实例的特征,一个静态的Event对象针对每个会话创建一个委托实例便可以完成上述的要求。遍历回调的方法便可以编写如下:
private void BroadcastEvent(CallbackEventArg e, ServerEventHanlder temp)
{
if (OnStatusChanged != null)
{
foreach (ServerEventHanlder handler in temp.GetInvocationList())
{
handler.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);
}
}
}
3)困扰了我半天的问题
上篇文章中,已经对duplex有了初步的认识,本以为本文的范例实现会很顺利呢,可有一个问题却困扰了我半天,在回调的时候非常不稳定,有时能回调4.5次,有时1,2次之后,再回调却没了响应,开始百思不得其解,因为开始几次可成功回调,为何会不稳定呢?经过好一番尝试,也没能解决,回调没有响应,肯定是客户端与服务端失去了连接,会话过期就会造成双方通讯连接的中断,经过分析,我的系统是这样的
会不会在Accept后Do()方法前的过程中会话过期了呢?后来经过验证,的确是此处的问题,解决方法是通过设置操作契约的IsTerminating来实现会话的维护,当一个操作契约的IsTerminating被设置为false的时候,该操作不会导致会话的中断,将IServer设计如下便解决了我的问题
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace Jillzhang.Event.Core
{
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ICallback))]
public interface IServer
{
[OperationContract(IsOneWay = true, IsInitiating = true, IsTerminating = false)]
void Accept();
[OperationContract(IsOneWay=true,IsInitiating=false,IsTerminating=false)]
void Do(string jobName);
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
void AddJob(Job job);
[OperationContract(IsOneWay=false,IsInitiating=false,IsTerminating=false)]
List<Job> GetJobs();
}
}
初步探讨一下异步
进程间通讯是一件很耗时的事情,如果同步执行会造成线程的阻塞,如果是在服务端,会降低服务的处理能力(这种说法可能有些问题,我会进一步求证,估计保留,经过查证多线程在服务端的好处在于提供对单个请求用多个线程处理的能力,从而防止完成一个请求之前,无法接受新的请求),如果是在客户端,会给用户带来不好的体验。下面就分别探讨一下如何实现服务端和客户端的异步。
在服务端,一个事件的异步可以通过delegate的BeginInvoke和EndInvoke来实现,具体方法可以参见示例项目Jillzhang.Event.Service中的Server对象的方法BroadcastEvent方法的实现
而在客户端,我们可以起多个线程,当然最方便快捷的办法就是使用BackGroundWorker后台线程来处理耗时比较长的操作了,具体实现也可以参考Jillzhang.Event.Client项目中的Form1.cs实现。
本文的参考资料
-
http://www.cnblogs.com/wayfarer/archive/2007/03/08/667865.html
-
http://www.cnblogs.com/caishiqi/archive/2007/10/05/914671.html
-
http://msdn.microsoft.com/msdnmag/issues/06/10/wcfessentials/default.aspx
本文中的范例
范例最终实现:/Files/jillzhang/Jillzhang.Event.rar
|