【架构之路之WCF全析(一)】--服务协定及消息模式
上周微软开公布会说.NET支持全然跨平台和并开放Core源代码的新闻,让我们顿时感到.NET要迎来它的春天。尽管早在几年前.NET就能开发Android和IOS,可是这次的跨平台把Linux都放到了微软战略之中,以后的.NET Developer就能够使用Vs开发Linux应用了,Developer又有了新的选择,从微软的战略转型也能够看出互联网已经步入到了新的模式,以后不再是PC的时代,移动互联和云时代已经到来。
近期做项目时使用到了WCF,项目把数据层和程序层进行了切割,相互之间的传输数据使用的就是WCF,这次的项目是为英国银行Enumis做的一整套银行的系统。从业务上总体划分为e-Banking、Corporate Panel、Etam、e-Commerce它们总体上构成了这家银行的一个网上管理系统,事实上这样的网上系统跟中国的银行是非常类似的,这些系统之间是通过相互之间提供数据或者接口来协同工作。
WCF全称是Windows Communication Fundation,提供了统一的,可用于建立安全、可靠地面向服务的应用的高效开发平台。WCF是基于属性的开发。它统一了各种分布式技术,也就是说它在应用程序和数据之间、应用程序与应用程序之间提供了一个桥梁,通过使用WCF来管理数据之间的互操作。这里说所的统一分布式技术说的是它把Windows中全部的通信技术做了整合封装,把它们都封装到了WCF架构里面。这样不管是採用何种通信方式仅仅须要加入一个WCF服务接口。然后全部基于WCF的应用都能够互相通信。这样增强了程序之间的灵活性。
如上图,在不使用WCF的时候要想实现之间的互通信可能就须要使用不同的技术来实现。这样在开发的时候就会耗费大量的时间来整合封装通信模块。假设採用WCF就能够降低模块的封装。使用WCF的属性来定义不同的通信接口,这样不同的程序之间或者程序与数据之间就能够通过WCF解耦。使得不同的模块间仅仅须要关注自己本身的服务就可以。
WCF不但封装了相互之间的通信服务,并且还封装了安全性和事务性的模块。为应用之间提供更加安全及高效的事务管理。
在WCF配置节中有三个基本的数据。分别为消息、服务和终结点。三个数据中服务的概念包括的最广,一个WCF能够称作一个服务,它类似于一个dll,每个service文件都会独立的生成一个服务。在使用服务的一方加入服务引用。当中的应用程序在调用服务时所发送的信息被称为一个消息,它是一个数据单元。和计算机网络中的消息是类似的,包括消息的正文和消息头。在应用程序一端想用调用服务就必须引用创建的服务,事实上是在配置文件里加入一个服务的终结点,每个服务的引用能够理解为一个终结点,在终结点中会配置服务所在的地址。互相通信的方式(如Http、Tcp等),服务的消息定义。
WCF是基于属性开发的,也就是说能够通过对类和方法採用属性标记法来指明服务及消息。在定义一个WCF时能够使用两种服务协定类和接口。这两种服务协定在产生的效果上是同样的。可是建议使用接口的方式,有更好的扩展性,并且有助于保持服务协定不变,在服务的版本号变更时仅仅须要又一次实现新接口就可以。
一、服务协定
WCF创建服务时是通过使用属性来指明的,在接口或类的定义上方使用ServiceContract(服务契约)来指明一个服务,在方法定义上方使用OperationContract(方法契约)来指明一则消息。这样就完毕了一个WCF的定义工作,假设使用的是接口那么须要实现对应的接口才可。
详细的定义方法来看以下的演示样例,使用的是接口方式来定义服务契约。
1、加入一个WCF
加入一个WCF应用程序集。然后在程序集中加入一个WCF,在加入时Item时能够选择WCF Service这样Vs会依据自带的模板新增一个接口的服务协定,并加入一个新的svc文件来实现接口。这个svc文件就是相应的wcf的实现类。另外也能够手动的编写一个服务协定接口。并实现相应的方法。例如以下代码:
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace Contracts { // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService2" in both code and config file together. [ServiceContract] public interface IService2 { [OperationContract] string DoWork(); } public class Service2 : IService2 { public string DoWork() { return "Hello WCF!"; } } }
2、使用服务
想要使用服务,在程序集中有两种方式,一种是通过加入服务的方法加入指定的服务,另外也能够手动在配置文件里加入终结点来实现服务的加入,
选中加入服务引用然后会弹出例如以下图显示的服务加入的界面。在文本框中能够手动输入服务地址,相同也能够点击Discover让vs自己主动识别新加入的服务。例如以下图:
Note:加入完服务后一定要手动的生成新服务,否在在加入服务引用时会报错,显示没有找到该服务的方法。
加入完服务的引用后程序集就能够直接使用服务的方法,这样的方法就好像是引入了一个新的dll文件一样,在使用时仅仅须要引入命名空间。直接就能够调用服务,这里实现一个简单的Helloworld程序:
using System; using System.Collections.Generic; using System.Linq; using System.ServiceModel; using System.Text; using System.Threading.Tasks; using Contracts; namespace ConsoleApplication2 { class Program { static void Main(string[] args) { IService2 service = new Service2(); Console.WriteLine(service.DoWork()); Console.Read(); } } }
执行结构例如以下图所看到的:
二、消息模式
上节介绍了有关WCF的服务的创建方法,并做了一个小的Demo来演示WCF的创建过程,WCF是採用属性标注开发的,在定义时相当的简单。
接下来我们对消息模式做具体的讨论。WCF提供了三种消息模式,各自是单向模式、请求/回答模式和双工模式,三种模式都支持client向服务端发送消息。不同的是单向模式仅仅支持消息的发送,不支持返回。
请求/回答模式支持client向服务端发送数据,并同一时候等待返回数据,它不支持服务端调用client。
双工模式则比較强大,不仅支持client调用服务端的方法,同一时候也支持服务端调用client的方法,功能强大。
1、单向模式
单向模式顾名思义是一种单向的请求,client向服务端发出消息请求后client就和服务端失去了联系,请求的一端不会关心是否返回结果继续往下运行。也就是说client发送请求后就会向下继续运行。不会等待服务端返回消息,并且服务端接收消息并运行服务,这样的单向的模式事实上是一种多线程下的操作,client发出消息后,client和服务端就会同一时候运行,这样它们之间就不会互相冲突,同一时候也是线程安全的,提高了运行效率。单向模式仅仅须要在方法声明中加入IsOneWay属性就可以。它就可以表示该消息的调用使用的是单向模式。例如以下代码:
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Web; using System.Text; namespace WcfService4 { // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together. [ServiceContract] public interface IService1 { //声明单向模式消息的方法 [OperationContract(IsOneWay = true)] void GetData(); [OperationContract] CompositeType GetDataUsingDataContract(CompositeType composite); // TODO: Add your service operations here } // Use a data contract as illustrated in the sample below to add composite types to service operations. [DataContract] public class CompositeType { bool boolValue = true; string stringValue = "Hello "; [DataMember] public bool BoolValue { get { return boolValue; } set { boolValue = value; } } [DataMember] public string StringValue { get { return stringValue; } set { stringValue = value; } } } } namespace WcfService4 { // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together. // NOTE: In order to launch WCF Test Client for testing this service, please select Service1.svc or Service1.svc.cs at the Solution Explorer and start debugging. //在一个service中实现WCF的接口协议 public class Service1 : IService1 { public void GetData() { System.Threading.Thread.Sleep(10000); Console.WriteLine("你好哈"); //return string.Format("You entered: {0}", value); } public CompositeType GetDataUsingDataContract(CompositeType composite) { if (composite == null) { throw new ArgumentNullException("composite"); } if (composite.BoolValue) { composite.StringValue += "Suffix"; } return composite; } } } //在client调用该服务,结果该线程并不会停顿而是继续运行client中的方法。 namespace ConsoleApplication1 { class Program { static void Main(string[] args) { ServiceReference1.IService1 service1=new Service1Client(); service1.GetData(); Console.WriteLine("This Main function"); Console.Read(); } } }
上面的演示样例演示了单向模式的详细过程,在定义接口服务协议的时候为方法制定了IsOneWay属性。这时就会使该消息变成为单向的请求模式。
client在调用服务的方法后,线程并没有停止请求,而是继续向下运行,这样的请求方式就是单向模式。
2、请求/答复模式
这样的模式是WCF消息模式的模式,也就是说client在服务端请求后会等待服务端运行完成并返回给client数据后。client才会继续向下运行。这样的方式相对单向模式来说灵活性差,可是安全性高,由于是单线程的所以安全性极高,适用于有数据返回的请求。
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Web; using System.Text; namespace WcfService4 { // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together. [ServiceContract] public interface IService1 { //声明请求/答复模式消息的方法 [OperationContract(IsOneWay = true)] string GetData(); } } namespace WcfService4 { // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together. // NOTE: In order to launch WCF Test Client for testing this service, please select Service1.svc or Service1.svc.cs at the Solution Explorer and start debugging. //在一个service中实现WCF的接口协议 public class Service1 : IService1 { public string GetData() { System.Threading.Thread.Sleep(10000); Console.WriteLine("你好哈"); return string.Format("You apply the WCF!"); } } } //在client调用该服务,结果该线程会依据WCF消息线程的时间停顿,当停顿完毕后才会继续向下运行。 namespace ConsoleApplication1 { class Program { static void Main(string[] args) { ServiceReference1.IService1 service1=new Service1Client(); service1.GetData(); Console.WriteLine("This Main function"); Console.Read(); } } }
3、双工模式
双工模式相交前两种模式来说相对复杂,它的请求方式同一时候适用于client和服务端,也就是说client能够请求服务端另外服务端也请求client,它们的调用关系是相互的,也就是client请求服务端的方法后,服务端同一时候请求client进行数据的交换这样的方法就须要使用双工模式。
也就是说双工模式同一时候附加了服务端与client的通信机制。在定义双工模式时须要在服务端指定一个回调函数(使用CallBackContract属性)同一时候定义两个接口。一个是服务端的消息接口,另外一个是client须要实现的接口(即:服务端的回调方法)。
服务端须要实现服务的接口,client须要实现client的服务协定接口。
定义双工服务。在定义时同一时候也要定义回调服务,也就是在client实现的服务,也就是说在声明服务时须要使用CallbackContract来指定回调的协定类,另外也要定义回话的模式,须要使用SessionMode.Required也就是必须使用回话的意思。详细定义方法例如以下:
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace Service { // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together. [ServiceContract(SessionMode = SessionMode.Required,CallbackContract = typeof(ICallBack))] public interface IService1 { [OperationContract] string ApplyData(int value); } public interface ICallBack { [OperationContract] string GetData(string data); } }
定义完服务后接下来须要在服务端和client实现服务的协定,这里须要特别注意。双工协定下,假设client和服务端相互调用的话,就会产生链式的死循环,循环就会导致死锁所以须要在实现服务的一方使用ServiceBehaviorAttribute特性的ConcurrencyMode属性将并发模式设为Reentrant或者Multiple均能够解决问题。
using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; using System.Runtime.Remoting.Contexts; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace Service { // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in both code and config file together. [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)] public class Service1 : IService1 { public string ApplyData(int value) { ICallBack callBack = OperationContext.Current.GetCallbackChannel<ICallBack>(); string strPrint=callBack.GetData("hahha"); return string.Format("You applyed the data is:{0},we getted data is: {1}",strPrint, value); } } }
using System; using System.Collections.Generic; using System.Linq; using System.ServiceModel; using System.Text; using System.Threading.Tasks; using ConsoleApplication1.ServiceReference1; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { CallBack callBack=new CallBack(); InstanceContext instanceContext=new InstanceContext(callBack); IService1 service1=new Service1Client(instanceContext); string strPrint=service1.ApplyData(32); Console.WriteLine(strPrint); Console.Read(); } } public class CallBack : ServiceReference1.IService1Callback { public string GetData(string data) { return data; } } }
打印结果:
双工协定相较前面两种实现起来会比較麻烦,所以在定义时须要有非常多地方须要注意,这里介绍几种刚開始学习的人须要注意的地方。
Note1:绑定方式
在建立双工协定时一定要注意使用支持双工协定的绑定,默认的basicHttpBinding绑定方式并不支持双工协定,所以在client加入双工服务协定时就会出错。找不到协议定义的接口。
能够使用wsDualHttpBinding绑定。在服务的终结点中声明绑定方法。绑定方法例如以下:
<endpoint address="" binding="wsDualHttpBinding" contract="Service.IService1">
Note2:回调导致的死锁
上面的演示样例中使用的是请求/回复协定,也就是在服务发出后要等待服务运行的一方完毕服务然后继续向下运行。可是上面的代码在运行后并没有出错。是由于在服务的实现一端使用了ServiceBehaviorAttribute属性指定了一种属性,假设不指定就会出现以下的错误:
这个错误出现就是由于服务在调用时出现了死锁(deadlock)的现象,由于client调用服务端的请求,然后服务端又调用client,这样继续下去就会导致恶性的循环,所以为了避免这样的情况。须要在服务实现的一方使用ServiceBehaviorAttribute特性的ConcurrencyMode属性将并发模式设为Reentrant或者Multiple
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)] public class CalculatorService : ICalculator { //省略实现 } [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)] public class CalculatorService : ICalculator { //省略实现 }
结语
本系列文章主要是来高速的入门学习WCF的应用,所以有些地方讲的较详细,不须要的能够略过。本文主要从WCF的基础開始重点介绍了WCF的一些术语及WCF三种主要的消息模式。这三种模式个有自己的优缺点,在使用时要依据详细情况详细分析。