.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证书
posted @ 2020-11-20 10:43  柠檬笔记  阅读(264)  评论(2编辑  收藏  举报