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)