.Net中关于SOA的三大组件之WCF
上一篇介绍了WebService,想想也是刚毕业那时候进入公司就接触的WebService,所以也相对熟悉一些。而WCF本人在实际应用中也很少使用,但平常的时候也有所了解。
WCF是.NetFramework3.5推出的,被称之为分布式服务的集大成者。它不像WebService只能寄宿在IIS上,它支持不同的协议,比如http、tcp、 IPC、 MSMQ、 p2p,并且支持不同的宿主:网站、控制台、winform、WindowsService。同时也可以支持双工通信。
当我们在VS上创建一个WCF项目时,其为我们生成Service1.svc和IService1.cs接口。下面来看一下一个WCF方法需要哪些东西吧。
// 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“IService1”。 [ServiceContract] public interface IService1 { [OperationContract] string GetData(int value); [OperationContract] CompositeType GetDataUsingDataContract(CompositeType composite); // TODO: 在此添加您的服务操作 } // 使用下面示例中说明的数据约定将复合类型添加到服务操作。 [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; } } }
上面代码是我们新建项目时为我们生成的,从中可以窥探一二。
- 首先WCF需要一个抽象接口,该接口需要添加特性:ServiceContract
- 接口里面的方法需要添加特性OperationContract才可以被调用
- 实体具有无参数构造函数时,可以不标记DataContract特性,调用时会将数据全部返回
- 实体没有无参数构造函数时,需要添加DataContract,当实体类上面有DataContract时,那么类中成员变量需要添加[DataMember],否则不可见
- 真实工作中,都会带上DataContract 和 DataMember
我们知道,将WCF应用部署到IIS上很简单,这里就不多说了。下面看一下将WCF通过TCP的方式托管在控制台或Winform或WindowsService上怎么做。其实也很简单,也是通过配置文件来配置,然后在程序中使用System.ServiceModel.ServiceHost来开启服务(注意权限问题)。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="MathServicebehavior"> <serviceDebug httpHelpPageEnabled="false"/> <serviceMetadata httpGetEnabled="false"/> <serviceTimeouts transactionTimeout="00:10:00"/> <serviceThrottling maxConcurrentCalls="1000" maxConcurrentInstances="1000" maxConcurrentSessions="1000"/> </behavior> <behavior name="CalculatorServicebehavior"> <serviceDebug httpHelpPageEnabled="false"/> <serviceMetadata httpGetEnabled="false"/> <serviceTimeouts transactionTimeout="00:10:00"/> <serviceThrottling maxConcurrentCalls="1000" maxConcurrentInstances="1000" maxConcurrentSessions="1000"/> </behavior> </serviceBehaviors> </behaviors> <bindings> <netTcpBinding> <binding name="tcpbinding"> <security mode="None"> <transport clientCredentialType="None" protectionLevel="None"/> </security> </binding> </netTcpBinding> </bindings> <services> <service name="SOA.WCF.Service.CalculatorService" behaviorConfiguration="CalculatorServicebehavior"> <host> <baseAddresses> <add baseAddress="net.tcp://localhost:11111/CalculatorService"/> </baseAddresses> </host> <endpoint address="" binding="netTcpBinding" bindingConfiguration="tcpbinding" contract="SOA.WCF.Interface.ICalculatorService"/> <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/> </service> <service name="SOA.WCF.Service.MathService" behaviorConfiguration="MathServicebehavior"> <host> <baseAddresses> <add baseAddress="net.tcp://localhost:11111/MathService"/> </baseAddresses> </host> <endpoint address="" binding="netTcpBinding" bindingConfiguration="tcpbinding" contract="SOA.WCF.Interface.IMathService"/> <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/> </service> </services> <!--<behaviors> <serviceBehaviors> <behavior name="MathServicebehavior"> <serviceDebug httpHelpPageEnabled="false"/> <serviceMetadata httpGetEnabled="false"/> <serviceTimeouts transactionTimeout="00:10:00"/> <serviceThrottling maxConcurrentCalls="1000" maxConcurrentInstances="1000" maxConcurrentSessions="1000"/> </behavior> </serviceBehaviors> </behaviors> <bindings> <basicHttpBinding> <binding name="httpbinding"/> </basicHttpBinding> </bindings> <services> <service name="SOA.WCF.Service.MathService" behaviorConfiguration="MathServicebehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:11113/MathService"/> </baseAddresses> </host> <endpoint address="" binding="basicHttpBinding" bindingConfiguration="httpbinding" contract="SOA.WCF.Interface.IMathService"/> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> </service> </services>--> </system.serviceModel> </configuration>
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ServiceModel; using SOA.WCF.Service; using SOA.WCF.Interface; using System.ServiceModel.Description; namespace SOA.Project { public class ServiceInit { public static void Process() { //ServiceHost对象 List<ServiceHost> hosts = new List<ServiceHost>() { new ServiceHost(typeof(MathService)), new ServiceHost(typeof(CalculatorService)) }; foreach (var host in hosts) { host.Opening += (s, e) => Console.WriteLine($"{host.GetType().Name} 准备打开"); host.Opened += (s, e) => Console.WriteLine($"{host.GetType().Name} 已经正常打开"); host.Closing += (s, e) => Console.WriteLine($"{host.GetType().Name} 准备关闭"); host.Closed += (s, e) => Console.WriteLine($"{host.GetType().Name} 准备关闭"); host.Open(); } Console.WriteLine("输入任何字符,就停止"); Console.Read(); foreach (var host in hosts) { host.Close(); } Console.Read(); #region MyRegion //using (ServiceHost host = new ServiceHost(typeof(MathService))) //{ // #region 程序配置 // host.AddServiceEndpoint(typeof(IMathService), new WSHttpBinding(), "http://127.0.0.1:9999/calculatorservice"); // if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null) // { // ServiceMetadataBehavior behavior = new ServiceMetadataBehavior(); // behavior.HttpGetEnabled = true; // behavior.HttpGetUrl = new Uri("http://127.0.0.1:9999/calculatorservice/metadata"); // host.Description.Behaviors.Add(behavior); // } // #endregion 程序配置 // host.Opened += (s, e) => // { // Console.WriteLine("MathService已经启动,按任意键终止服务!"); // }; // host.Open(); // Console.Read(); //} #endregion } private static void Host_Closing(object sender, EventArgs e) { throw new NotImplementedException(); } } }
那为什么采用http方式部署到IIS那么简单,我们还要使用其他协议来部署呢?这个当然有各个协议的优点和具体的用处了,例如TCP协议速度快点,但是不能穿透防火墙,可是其是有状态的而http是无状态的。而且通常WCF会和WindowsService结合被用来做定时调度任务,关于任务调度可以看我这篇博文 :https://www.cnblogs.com/jesen1315/p/11653181.html 。
因为WCF可以采用TCP、Pipeline协议,所以WCF还可以做双工通信,其核心思路就是回调,具体做法:
- 在服务接口上约定,CallbackContract(回调接口)
- 回调接口里面方法需要IsOneWay=true
- 启动服务,客户端引用服务,实现回调接口
- 调用服务时,讲回调方法传递进去
- 不仅是客户端可以向服务端发消息,而且服务端可以主动向客户端发消息
下面我们来看下具体的代码
[ServiceContract(CallbackContract = typeof(ICallBack))] public interface ICalculatorService { [OperationContract(IsOneWay = true)] void Plus(int x, int y); } /// <summary> /// 不需要协议 是个约束,由客户端实现 /// </summary> public interface ICallBack { [OperationContract(IsOneWay = true)] void Show(int m, int n, int result); } public class CalculatorService : ICalculatorService { /// <summary> /// 完成计算,然后去回调 /// </summary> /// <param name="x"></param> /// <param name="y"></param> public void Plus(int x, int y) { int result = x + y; Thread.Sleep(2000); ICallBack callBack = OperationContext.Current.GetCallbackChannel<ICallBack>(); callBack.Show(x, y, result); } }
最后关于WCF的安全,通常WCF一般是给后端用的,也是在内网中使用,但是也不排除有些公司提供给第三方的接口采用的是WCF服务,像我现在公司就是,所以也要注意到其安全性。那么关于WCF的一般可以有以下的方式,具体怎么做就不多说了。
- 没有身份验证--局域网后台调用
- 发布口令--请求式加个参数(事先约定)
- soapheader--用户的账号密码
- Windows 身份验证
- X509证书