上周微软开发布会说.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配置节中有三个主要的数据,分别为消息、服务和终结点。三个数据中服务的概念包含的最广,一个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
- {
-
- [ServiceContract]
- public interface IService2
- {
- [OperationContract]
- string DoWork();
- }
-
- public class Service2 : IService2
- {
- public string DoWork()
- {
- return "Hello 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();
- }
- }
- }
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提供了三种消息模式,分别是单向模式、请求/回答模式和双工模式,三种模式都支持客户端向服务端发送消息,不同的是单向模式只支持消息的发送,不支持返回。请求/回答模式支持客户端向服务端发送数据,并同时等待返回数据,它不支持服务端调用客户端。双工模式则比较强大,不仅支持客户端调用服务端的方法,同时也支持服务端调用客户端的方法,功能强大。
消息模式导图
1、单向模式
单向模式顾名思义是一种单向的请求,客户端向服务端发出消息请求后客户端就和服务端失去了联系,请求的一端不会关心是否返回结果继续往下执行。也就是说客户端发送请求后就会向下继续执行,不会等待服务端返回消息,而且服务端接收消息并执行服务,这种单向的模式其实是一种多线程下的操作,客户端发出消息后,客户端和服务端就会同时执行,这样它们之间就不会互相冲突,同时也是线程安全的,提高了执行效率。
单向模式只需要在方法声明中添加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
- {
-
- [ServiceContract]
- public interface IService1
- {
-
- [OperationContract(IsOneWay = true)]
- void GetData();
-
- [OperationContract]
- CompositeType GetDataUsingDataContract(CompositeType composite);
-
-
- }
-
-
-
- [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
- {
-
-
-
- public class Service1 : IService1
- {
- public void GetData()
- {
- System.Threading.Thread.Sleep(10000);
- Console.WriteLine("你好哈");
-
- }
-
- public CompositeType GetDataUsingDataContract(CompositeType composite)
- {
- if (composite == null)
- {
- throw new ArgumentNullException("composite");
- }
- if (composite.BoolValue)
- {
- composite.StringValue += "Suffix";
- }
- return composite;
- }
- }
- }
-
-
-
- namespace ConsoleApplication1
- {
- class Program
- {
- static void Main(string[] args)
- {
- ServiceReference1.IService1 service1=new Service1Client();
- service1.GetData();
- Console.WriteLine("This Main function");
- Console.Read();
- }
- }
- }
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;
}
}
}
//在客户端调用该服务,结果该线程并不会停顿而是继续执行客户端中的方法。
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
ServiceReference1.IService1 service1=new Service1Client();
service1.GetData();
Console.WriteLine("This Main function");
Console.Read();
}
}
}
上面的示例演示了单向模式的具体过程,在定义接口服务协议的时候为方法制定了IsOneWay属性,这时就会使该消息变成为单向的请求模式。客户端在调用服务的方法后,线程并没有停止请求,而是继续向下执行,这种请求方式就是单向模式。
2、请求/答复模式
这种模式是WCF消息模式的模式,也就是说客户端在服务端请求后会等待服务端执行完毕并返回给客户端数据后,客户端才会继续向下执行,这种方式相对单向模式来说灵活性差,但是安全性高,因为是单线程的所以安全性极高,适用于有数据返回的请求。
- 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
- {
-
- [ServiceContract]
- public interface IService1
- {
-
- [OperationContract(IsOneWay = true)]
- string GetData();
-
- }
-
- }
-
- namespace WcfService4
- {
-
-
-
- public class Service1 : IService1
- {
- public string GetData()
- {
- System.Threading.Thread.Sleep(10000);
- Console.WriteLine("你好哈");
- return string.Format("You apply the WCF!");
- }
- }
- }
-
-
-
- namespace ConsoleApplication1
- {
- class Program
- {
- static void Main(string[] args)
- {
- ServiceReference1.IService1 service1=new Service1Client();
- service1.GetData();
- Console.WriteLine("This Main function");
- Console.Read();
- }
- }
- }
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!");
}
}
}
//在客户端调用该服务,结果该线程会根据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、双工模式
双工模式相交前两种模式来说相对复杂,它的请求方式同时适用于客户端和服务端,也就是说客户端可以请求服务端另外服务端也请求客户端,它们的调用关系是相互的,也就是客户端请求服务端的方法后,服务端同时请求客户端进行数据的交换这种方法就需要使用双工模式。
也就是说双工模式同时附加了服务端与客户端的通信机制。在定义双工模式时需要在服务端指定一个回调函数(使用CallBackContract属性)同时定义两个接口,一个是服务端的消息接口,另外一个是客户端需要实现的接口(即:服务端的回调方法)。服务端需要实现服务的接口,客户端需要实现客户端的服务协定接口。
定义双工服务,在定义时同时也要定义回调服务,也就是在客户端实现的服务,也就是说在声明服务时需要使用CallbackContract来指定回调的协定类,另外也要定义回话的模式,需要使用SessionMode.Required也就是必须使用回话的意思。具体定义方法如下:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Runtime.Serialization;
- using System.ServiceModel;
- using System.Text;
-
- namespace Service
- {
-
- [ServiceContract(SessionMode = SessionMode.Required,CallbackContract = typeof(ICallBack))]
- public interface IService1
- {
- [OperationContract]
- string ApplyData(int value);
- }
-
- public interface ICallBack
- {
- [OperationContract]
- string GetData(string data);
- }
- }
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);
}
}
定义完服务后接下来需要在服务端和客户端实现服务的协定,这里需要特别注意。双工协定下,如果客户端和服务端相互调用的话,就会产生链式的死循环,循环就会导致死锁所以需要在实现服务的一方使用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
- {
-
- [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.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;
- }
- }
- }
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绑定方式并不支持双工协定,所以在客户端添加双工服务协定时就会出错,找不到协议定义的接口。可以使用wsDualHttpBinding绑定,在服务的终结点中声明绑定方法,绑定方法如下:
- <endpoint address="" binding="wsDualHttpBinding" contract="Service.IService1">
<endpoint address="" binding="wsDualHttpBinding" contract="Service.IService1">
Note2:回调导致的死锁
上面的示例中使用的是请求/回复协定,也就是在服务发出后要等待服务执行的一方完成服务然后继续向下执行,但是上面的代码在运行后并没有出错,是因为在服务的实现一端使用了ServiceBehaviorAttribute属性指定了一种属性,如果不指定就会出现下面的错误:
这个错误出现就是因为服务在调用时出现了死锁(deadlock)的现象,因为客户端调用服务端的请求,然后服务端又调用客户端,这样继续下去就会导致恶性的循环,所以为了避免这种情况,需要在服务实现的一方使用ServiceBehaviorAttribute特性的ConcurrencyMode属性将并发模式设为Reentrant或者Multiple
- [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
- public class CalculatorService : ICalculator
- {
-
- }
-
- [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
- public class CalculatorService : ICalculator
- {
-
- }
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
public class CalculatorService : ICalculator
{
//省略实现
}
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class CalculatorService : ICalculator
{
//省略实现
}
结语
本系列文章主要是来快速的入门学习WCF的应用,所以有些地方讲的较详细,不需要的可以略过。本文主要从WCF的基础开始重点介绍了WCF的一些术语及WCF三种基本的消息模式,这三种模式个有自己的优缺点,在使用时要根据具体情况具体分析。
版权声明:本文为博主原创文章,未经博主允许不得转载。