Azure Service Bus -- 深入实践 Service Bus Relay(中继)

Azure Service Bus通过为服务提供了一套通用的命名规范简化了许多通信难题,在独立于网络拓扑和配置的节点之间提供直接或间接的通信。
Service Bus允许WCF应用程序监听公共网络地址,即使其位于NAT或网络防火墙后方。该功能使得应用程序的通信可以无关于其网络结构。使用Service Bus便无需编写与维护复杂的逻辑和代码来跨越不同的网络通信。

如图,Azure Service Bus的实现原理:

主要过程为:
1-2. 已有服务程序(如WCF)连接Azure上的Access Control(连接控制)服务,获得密钥。
3. 已有服务程序使用密钥连接Service Bus Relay(中继)服务,将本地服务注册到Service Bus Relay(中继)上,使得本地服务具备基于互联网的公开访问端口。
4-5. 客户端应用程序连接Azure上的Access Control(连接控制)服务,获得密钥。
6. 客户端使用密钥连接注册在Service Bus Relay(中继)上的指定服务。
7. Service Bus Relay(中继)将6的请求转发到具体的本地服务程序处理。
8-9. 本地服务程序处理完请求后,返回结果经Service Bus Relay(中继)最后达到客户端应用。

因此,Service Bus Relay(中继)服务主要能带来以下利好:
1. 处于不同网络(地域)的客户端能自如的建立实时通信(不需要独立公网IP,不需要特殊的网络安全配置等)
2. 本地已有的WCF服务能够通过Service Bus Relay(中继)暴露到互联网上,供异地的客户端随时连接、调用。

本篇将深入实践Service Bus Relay(中继)服务,分为两个部分:
1. 通过Service Bus Relay(中继)实现WCF服务的暴露和访问(包括本地WCF访问)
2. 通过传统WCF配置方式来完成Service Bus Relay(中继)服务调用实现。

关于WCF服务,在传统开发中应用非常常见,其定义为:https://msdn.microsoft.com/zh-cn/library/vstudio/ms731082(v=vs.90).aspx

 简而言之,WCF是.Net框架下符合SOAP标准的web service,其服务可以被不同开发语言实现的客户端调用。更多内容请参见:https://msdn.microsoft.com/zh-cn/library/vstudio/ms731190(v=vs.90).aspx

 

实践一、通过Service Bus Relay(中继)实现WCF服务的暴露和访问

步骤一:创建Service Bus NameSpace

登录到Azure管理主页: Windows Azure Management Portal.

选择左边栏中的Service Bus.

 

通过左下角的New按钮开始创建Service Bus Relay (service bus --> Relay --> Quick Create),将要创建的namespace需要一个唯一的标识名称,如mysbtest,选择region后,点击Create A Relay完成创建

创建需要一段时间完成,创建过程中可以通过管理主页底部的实时消息查看状态,如下图。创建完成后,新建的namespace状态为Active。

 

创建完成后,选中刚刚创建的namespace,点击下方的Connection Information按钮。

 

在弹出的页面中,复制issuer信息和key信息,保存到本地记事本文件,后续需要使用。创建的namespace名称也需要保存到本地,供后续使用。

 

 

步骤二:开发Service Bus服务

以Admin身份打开Visual Studio,创建一个新的工程(File --> New --> Project),选择Console Application,工程名为ServiceBusDemo,项目名为Service。

 

点击OK创建完后,右键解决方案,Add --> New Project添加一个Console Application,项目名为Client

 

添加完成后,项目工程如下

 

逐一右键点击项目Client和Service,选择Property,确认选用的Target framework版本为.Net Framework 4

 

右键项目Service,选择Add --> New Item, 命名为IProblemSolver.cs

 

同样的方法在Service项目中添加文件ProblemSolver.cs

 

右键点击Service项目的References,添加引用system.servicemodel。(Add --> Add Reference)

 

右键点击Service项目,选择Manage NuGet Packages在线添加新的引用

 

在线搜索“windowsazure”,找到service bus相关的安装包,点击install安装。

 

安装完成后,双击Service项目中的IProblemSolver.cs,添加以下代码。

    [ServiceContract(Namespace = "urn:ps")]
    interface IProblemSolver
    {
        [OperationContract]
        int AddNumbers(int a, int b);
    }

    interface IProblemSolverChannel : IProblemSolver, IClientChannel { }

安装完成后,双击Service项目中的ProblemSolver.cs,添加以下代码。

     class ProblemSolver : IProblemSolver
    {
        public int AddNumbers(int a, int b)
        {
            return a + b;
        }
    }

双击Service项目中的Program.cs文件,添加以下代码:

        static void Main(string[] args)
        {
            ServiceHost sh = new ServiceHost(typeof(ProblemSolver));

            sh.AddServiceEndpoint(
               typeof(IProblemSolver), new NetTcpBinding(),
               "net.tcp://localhost:9358/solver");

            sh.AddServiceEndpoint(
               typeof(IProblemSolver), new NetTcpRelayBinding(),
               ServiceBusEnvironment.CreateServiceUri("sb", "mysbtest", "solver"))
                .Behaviors.Add(new TransportClientEndpointBehavior
                {
                    TokenProvider = TokenProvider.CreateSharedSecretTokenProvider("owner", "EuwKep4k8vbkoLdlIgCyRynrepN2DQcAQH9E77wUASU=")
                });

            sh.Open();
            for(int i=0; i<sh.Description.Endpoints.Count;i++)
            {
                Console.WriteLine("Exposed Service endpoint {0} : {1}", i, sh.Description.Endpoints[i].Address);         
            }

 
            Console.WriteLine("Press ENTER to close");
            Console.ReadLine();
            sh.Close();
        }

更改上述代码中的mysbtest和EuwKep4k8vbkoLdlIgCyRynrepN2DQcAQH9E77wUASU=为步骤一中创建的Service Bus namespace名称和保存的key。

右键选中Service项目,选择“Set as startup Project”,然后右键选中Service项目,rebuild,确认没有错误。

F5本地启动Service项目,得到如下效果:

 

 

步骤三:开发Service Bus客户端

同步骤二,右键点击Client项目的References,添加引用system.servicemodel。(Add --> Add Reference)

 

右键点击Client项目,选择Manage NuGet Packages在线添加新的引用

同上,在线搜索“windowsazure”,找到service bus相关的安装包,点击install安装到Client项目中。

右键项目Client项目,选择Add --> New Item, 命名为IProblemSolver.cs

双击Client项目中的IProblemSolver.cs,添加以下代码。

 

    [ServiceContract(Namespace = "urn:ps")]

    interface IProblemSolver

    {

        [OperationContract]

        int AddNumbers(int a, int b);

    }

 

    interface IProblemSolverChannel : IProblemSolver, IClientChannel { }

安装完成后,双击Client项目中的Program.cs,添加以下代码。

 

 

        static void Main(string[] args)

        {

            var cf0 = new ChannelFactory<IProblemSolverChannel>(

                new NetTcpBinding(),

                new EndpointAddress("net.tcp://localhost:9358/solver"));

 

            Console.WriteLine("Trying to consume service through local WCF channel.");

            using (var ch0 = cf0.CreateChannel())

            {

                Console.WriteLine("{0}+{1}={2}",4,5,ch0.AddNumbers(4, 5));

            }

 

            var cf = new ChannelFactory<IProblemSolverChannel>(

                new NetTcpRelayBinding(),

                new EndpointAddress(ServiceBusEnvironment.CreateServiceUri("sb", "mysbtest", "solver")));

 

            cf.Endpoint.Behaviors.Add(new TransportClientEndpointBehavior { TokenProvider = TokenProvider.CreateSharedSecretTokenProvider("owner", "EuwKep4k8vbkoLdlIgCyRynrepN2DQcAQH9E77wUASU=") });

 

            Console.WriteLine("Trying to consume service through Service Bus channel.");

            using (var ch = cf.CreateChannel())

            {

                Console.WriteLine("{0}+{1}={2}", 4, 5, ch.AddNumbers(4, 5));

            }

            Console.Read();

        }

 

更改上述代码中的mysbtest和EuwKep4k8vbkoLdlIgCyRynrepN2DQcAQH9E77wUASU=为步骤一中创建的Service Bus namespace名称和保存的key。

右键选中Client项目,rebuild,确认没有错误。

 

步骤四:验证、分析、拓展

F5启动Service项目,等待Service项目启动好后得到如下console:

 

 

然后右键点击Client项目,选择DebugàStart New Instance得到Client对应的console及效果

 

 

至此,Service Bus Relay的demo已经实现。开发者可以尝试将Service和Client编译出的Debug文件放在两台处在不同网络环境下的机器中(如本机和cloud上虚机),先启动Service.exe,然后再另外一台机器上启动Client.exe,观察Service Bus Relay的效果。

 

实践二、通过传统WCF配置方式来完成Service Bus Relay(中继)服务调用

 对于传统WCF开发和配置,可以使用完全配置的方式来实现WCF服务,如https://msdn.microsoft.com/zh-cn/library/vstudio/ms733932(v=vs.90).aspx

 因此,在使用Service Bus Relay时,也可以完全通过配置的方式来讲本地的服务暴露到internet中。其实现方式如下:

按照上面同样的方法,添加两个Console程序:ClientConfig和ServiceConfig

在ServiceConfig中添加契约和实现:IProblemSolver和ProblemSolver。

在ClientConfig中添加契约:IProblemSolver

依次在两个Console项目中通过Nuget安装Service Bus引用包。

在ServiceConfig项目中的Program.cs中添加一下代码:

namespace ServiceConfig
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Title = "service";

            ServiceHost sh = new ServiceHost(typeof(ProblemSolver));
            sh.Open();

            for (int i = 0; i < sh.Description.Endpoints.Count; i++)
            {
                Console.WriteLine("Exposed Service endpoint {0} : {1}", i, sh.Description.Endpoints[i].Address);
            }

            Console.WriteLine("Press ENTER to close");
            Console.ReadLine();
            sh.Close();

        }
    }
}

 在ServiceConfig项目中添加app.config,并添加以下配置:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <system.serviceModel>
   
    <extensions>
      <behaviorExtensions>
        <add name="transportClientEndpointBehavior" type="Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      </behaviorExtensions>
      <bindingExtensions>
        <add name="netTcpRelayBinding" type="Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      </bindingExtensions>
    </extensions>
   
    <services>
      <service name="ServiceConfig.ProblemSolver">
        <endpoint contract="ServiceConfig.IProblemSolver"
                binding="netTcpBinding"
                address="net.tcp://localhost:9358/solver"/>
        <endpoint contract="ServiceConfig.IProblemSolver"
                  binding="netTcpRelayBinding"
                  address="sb://testsb1017.servicebus.windows.net/solver"
                  behaviorConfiguration="sbTokenProvider"/>
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="sbTokenProvider">
          <transportClientEndpointBehavior>
            <tokenProvider>
              <sharedSecret issuerName="owner" issuerSecret="××××××××××××××××××××××××=" />
            </tokenProvider>
          </transportClientEndpointBehavior>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>

</configuration>

 

同上,在ClientConfig项目中的Program.cs中添加一下代码:

namespace ClientConfig
{
    class Program
    {
        static void Main(string[] args)
        {
            var cf2 = new ChannelFactory<IProblemSolverChannel>("localsolver");
            using (var ch = cf2.CreateChannel())
            {
                Console.WriteLine("Trying to consume service through local WCF channel.");
                Console.WriteLine("4 + 5 = {0}", ch.AddNumbers(4, 5));
            }

            var cf = new ChannelFactory<IProblemSolverChannel>("solver");                
            using (var ch = cf.CreateChannel())
            {
                Console.WriteLine("Trying to consume service through Service Bus channel.");
                Console.WriteLine("4 + 5 = {0}",ch.AddNumbers(4, 5));
            }

            Console.Read();
        }
    }
}

  在ClientConfig项目中添加app.config,并添加以下配置:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>

    <extensions>
      <behaviorExtensions>
        <add name="transportClientEndpointBehavior" type="Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      </behaviorExtensions>
      <bindingExtensions>
        <add name="netTcpRelayBinding" type="Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      </bindingExtensions>
    </extensions>
   
    <client>
      <endpoint name="solver" contract="ClientConfig.IProblemSolver"
                binding="netTcpRelayBinding"
                address="sb://testsb1017.servicebus.windows.net/solver"
                behaviorConfiguration="sbTokenProvider"/>
      <endpoint name="localsolver" contract="ClientConfig.IProblemSolver"
        binding="netTcpBinding"
        address="net.tcp://localhost:9358/solver"/>
    </client>
   
    <behaviors>
      <endpointBehaviors>
        <behavior name="sbTokenProvider">
          <transportClientEndpointBehavior>
            <tokenProvider>
              <sharedSecret issuerName="owner" issuerSecret="×××××××××××××××××××××=" />
            </tokenProvider>
          </transportClientEndpointBehavior>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

先启动ServiceConfig项目,然后启动ClientConfig项目,可以得到以下实践效果:

 

小结

WCF服务可以通过Service Bus Relay建立不同客户端之间的实时互联。

基于Service Bus Relay的服务程序的基础编程和WCF一致,可以使用代码,也可以基于配置。

同一个服务可以同时暴露在局域网内(传统WCF)和互联网上(基于Service Bus Relay)